зеркало из https://github.com/mozilla/gecko-dev.git
Merge mozilla-central to inbound. a=merge CLOSED TREE
This commit is contained in:
Коммит
331818d5aa
|
@ -2128,6 +2128,10 @@ file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
|||
return;
|
||||
}
|
||||
|
||||
// Explicitly set the direction of the popup because automplete.xml
|
||||
// expects this.
|
||||
this.style.direction = (RTL_UI ? "rtl" : "ltr");
|
||||
|
||||
// Make the popup span the width of the window. First, set its width.
|
||||
let documentRect =
|
||||
window.windowUtils
|
||||
|
|
|
@ -117,6 +117,8 @@ function triggerAutofillAndCheckProfile(profile) {
|
|||
if (element.tagName == "INPUT" && element.type == "text") {
|
||||
ok(event instanceof InputEvent,
|
||||
`"input" event should be dispatched with InputEvent interface on ${element.tagName}`);
|
||||
is(event.inputType, "insertReplacementText",
|
||||
"inputType value should be \"insertReplacementText\"");
|
||||
} else {
|
||||
ok(event instanceof Event && !(event instanceof UIEvent),
|
||||
`"input" event should be dispatched with Event interface on ${element.tagName}`);
|
||||
|
|
|
@ -74,6 +74,8 @@ async function confirmClear(selector) {
|
|||
'"input" event should be never cancelable');
|
||||
is(event.bubbles, true,
|
||||
'"input" event should always bubble');
|
||||
is(event.inputType, "insertReplacementText",
|
||||
'inputType value should be "insertReplacementText"');
|
||||
resolve();
|
||||
}, {once: true})
|
||||
);
|
||||
|
|
|
@ -52,6 +52,8 @@ function checkElementFilled(element, expectedvalue) {
|
|||
if (element.tagName == "INPUT" && element.type == "text") {
|
||||
ok(event instanceof InputEvent,
|
||||
`"input" event should be dispatched with InputEvent interface on ${element.name}`);
|
||||
is(event.inputType, "insertReplacementText",
|
||||
"inputType value should be \"insertReplacementText\"");
|
||||
} else {
|
||||
ok(event instanceof Event && !(event instanceof UIEvent),
|
||||
`"input" event should be dispatched with Event interface on ${element.name}`);
|
||||
|
|
|
@ -69,7 +69,8 @@
|
|||
|
||||
.customizationmode-checkbox:not(:-moz-lwtheme),
|
||||
.customizationmode-button {
|
||||
color: rgb(71, 71, 71);
|
||||
/* !important overrides :hover:active color from button.css on Mac */
|
||||
color: rgb(71, 71, 71) !important;
|
||||
}
|
||||
|
||||
#customization-reset-button,
|
||||
|
@ -79,7 +80,7 @@
|
|||
}
|
||||
|
||||
#customization-done-button {
|
||||
color: #fff;
|
||||
color: #fff !important;
|
||||
font-weight: 700;
|
||||
border-color: #0060df;
|
||||
background-color: #0a84ff;
|
||||
|
|
|
@ -57,7 +57,6 @@
|
|||
.downloadsPanelFooterButton:hover:active,
|
||||
.downloadsPanelFooterButton[open="true"] {
|
||||
outline: 1px solid var(--arrowpanel-dimmed-further);
|
||||
box-shadow: 0 1px 0 hsla(210,4%,10%,.05) inset;
|
||||
}
|
||||
|
||||
.downloadsPanelFooterButton > .button-box {
|
||||
|
@ -177,7 +176,7 @@
|
|||
border: none;
|
||||
background: transparent;
|
||||
padding: 0;
|
||||
color: inherit;
|
||||
color: inherit !important /* !important overrides button.css on Mac and Linux */;
|
||||
}
|
||||
|
||||
.downloadButton > .button-box > .button-icon {
|
||||
|
|
|
@ -596,7 +596,11 @@ function createHighlightButton(highlighterName, id) {
|
|||
isChecked(toolbox) {
|
||||
// if the inspector doesn't exist, then the highlighter has not yet been connected
|
||||
// to the front end.
|
||||
const inspectorFront = toolbox.target.getCachedFront("inspector");
|
||||
// TODO: we are using target._inspector here, but we should be using
|
||||
// target.getCachedFront. This is a temporary solution until the inspector no
|
||||
// longer relies on the toolbox and can be destroyed the same way any other
|
||||
// front would be. Related: #1487677
|
||||
const inspectorFront = toolbox.target._inspector;
|
||||
if (!inspectorFront) {
|
||||
// initialize the inspector front asyncronously. There is a potential for buggy
|
||||
// behavior here, but we need to change how the buttons get data (have them
|
||||
|
|
|
@ -199,62 +199,61 @@ const TargetFactory = exports.TargetFactory = {
|
|||
* If the target is a local Firefox tab, a reference to the firefox
|
||||
* frontend tab object.
|
||||
*/
|
||||
function Target({ activeTab, client, chrome, tab = null }) {
|
||||
EventEmitter.decorate(this);
|
||||
this.destroy = this.destroy.bind(this);
|
||||
this._onTabNavigated = this._onTabNavigated.bind(this);
|
||||
this.activeConsole = null;
|
||||
class Target extends EventEmitter {
|
||||
constructor({ client, chrome, activeTab, tab = null }) {
|
||||
if (!activeTab) {
|
||||
throw new Error("Cannot instanciate target without a non-null activeTab");
|
||||
}
|
||||
|
||||
if (!activeTab) {
|
||||
throw new Error("Cannot instanciate target without a non-null activeTab");
|
||||
}
|
||||
this.activeTab = activeTab;
|
||||
super();
|
||||
|
||||
this._url = this.form.url;
|
||||
this._title = this.form.title;
|
||||
this.destroy = this.destroy.bind(this);
|
||||
this._onTabNavigated = this._onTabNavigated.bind(this);
|
||||
this.activeConsole = null;
|
||||
this.activeTab = activeTab;
|
||||
|
||||
this._client = client;
|
||||
this._chrome = chrome;
|
||||
this._url = this.form.url;
|
||||
this._title = this.form.title;
|
||||
|
||||
// When debugging local tabs, we also have a reference to the Firefox tab
|
||||
// This is used to:
|
||||
// * distinguish local tabs from remote (see target.isLocalTab)
|
||||
// * being able to hookup into Firefox UI (see Hosts)
|
||||
if (tab) {
|
||||
this._tab = tab;
|
||||
this._setupListeners();
|
||||
this._client = client;
|
||||
this._chrome = chrome;
|
||||
|
||||
// When debugging local tabs, we also have a reference to the Firefox tab
|
||||
// This is used to:
|
||||
// * distinguish local tabs from remote (see target.isLocalTab)
|
||||
// * being able to hookup into Firefox UI (see Hosts)
|
||||
if (tab) {
|
||||
this._tab = tab;
|
||||
this._setupListeners();
|
||||
}
|
||||
|
||||
// isBrowsingContext is true for all target connected to an actor that inherits from
|
||||
// BrowsingContextTargetActor. It happens to be the case for almost all targets but:
|
||||
// * legacy add-ons (old bootstrapped add-ons)
|
||||
// * content process (browser content toolbox)
|
||||
// * xpcshell debugging (it uses ParentProcessTargetActor, which inherits from
|
||||
// BrowsingContextActor, but doesn't have any valid browsing
|
||||
// context to attach to.)
|
||||
// Starting with FF64, BrowsingContextTargetActor exposes a traits to help identify
|
||||
// the target actors inheriting from it. It also help identify the xpcshell debugging
|
||||
// target actor that doesn't have any valid browsing context.
|
||||
// (Once FF63 is no longer supported, we can remove the `else` branch and only look
|
||||
// for the traits)
|
||||
if (this.form.traits && ("isBrowsingContext" in this.form.traits)) {
|
||||
this._isBrowsingContext = this.form.traits.isBrowsingContext;
|
||||
} else {
|
||||
this._isBrowsingContext = !this.isLegacyAddon && !this.isContentProcess && !this.isWorkerTarget;
|
||||
}
|
||||
|
||||
// Cache of already created targed-scoped fronts
|
||||
// [typeName:string => Front instance]
|
||||
this.fronts = new Map();
|
||||
// Temporary fix for bug #1493131 - inspector has a different life cycle
|
||||
// than most other fronts because it is closely related to the toolbox.
|
||||
// TODO: remove once inspector is separated from the toolbox
|
||||
this._inspector = null;
|
||||
}
|
||||
|
||||
// isBrowsingContext is true for all target connected to an actor that inherits from
|
||||
// BrowsingContextTargetActor. It happens to be the case for almost all targets but:
|
||||
// * legacy add-ons (old bootstrapped add-ons)
|
||||
// * content process (browser content toolbox)
|
||||
// * xpcshell debugging (it uses ParentProcessTargetActor, which inherits from
|
||||
// BrowsingContextActor, but doesn't have any valid browsing
|
||||
// context to attach to.)
|
||||
// Starting with FF64, BrowsingContextTargetActor exposes a traits to help identify
|
||||
// the target actors inheriting from it. It also help identify the xpcshell debugging
|
||||
// target actor that doesn't have any valid browsing context.
|
||||
// (Once FF63 is no longer supported, we can remove the `else` branch and only look
|
||||
// for the traits)
|
||||
if (this.form.traits && ("isBrowsingContext" in this.form.traits)) {
|
||||
this._isBrowsingContext = this.form.traits.isBrowsingContext;
|
||||
} else {
|
||||
this._isBrowsingContext = !this.isLegacyAddon && !this.isContentProcess && !this.isWorkerTarget;
|
||||
}
|
||||
|
||||
// Cache of already created targed-scoped fronts
|
||||
// [typeName:string => Front instance]
|
||||
this.fronts = new Map();
|
||||
// Temporary fix for bug #1493131 - inspector has a different life cycle
|
||||
// than most other fronts because it is closely related to the toolbox.
|
||||
// TODO: remove once inspector is separated from the toolbox
|
||||
this._inspector = null;
|
||||
}
|
||||
|
||||
exports.Target = Target;
|
||||
|
||||
Target.prototype = {
|
||||
/**
|
||||
* Returns a promise for the protocol description from the root actor. Used
|
||||
* internally with `target.actorHasMethod`. Takes advantage of caching if
|
||||
|
@ -291,7 +290,7 @@ Target.prototype = {
|
|||
* "events": {}
|
||||
* }
|
||||
*/
|
||||
getActorDescription: async function(actorName) {
|
||||
async getActorDescription(actorName) {
|
||||
if (this._protocolDescription &&
|
||||
this._protocolDescription.types[actorName]) {
|
||||
return this._protocolDescription.types[actorName];
|
||||
|
@ -299,7 +298,7 @@ Target.prototype = {
|
|||
const description = await this.client.mainRoot.protocolDescription();
|
||||
this._protocolDescription = description;
|
||||
return description.types[actorName];
|
||||
},
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a boolean indicating whether or not the specific actor
|
||||
|
@ -308,12 +307,12 @@ Target.prototype = {
|
|||
* @param {String} actorName
|
||||
* @return {Boolean}
|
||||
*/
|
||||
hasActor: function(actorName) {
|
||||
hasActor(actorName) {
|
||||
if (this.form) {
|
||||
return !!this.form[actorName + "Actor"];
|
||||
}
|
||||
return false;
|
||||
},
|
||||
}
|
||||
|
||||
/**
|
||||
* Queries the protocol description to see if an actor has
|
||||
|
@ -326,14 +325,14 @@ Target.prototype = {
|
|||
* @param {String} methodName
|
||||
* @return {Promise}
|
||||
*/
|
||||
actorHasMethod: function(actorName, methodName) {
|
||||
actorHasMethod(actorName, methodName) {
|
||||
return this.getActorDescription(actorName).then(desc => {
|
||||
if (desc && desc.methods) {
|
||||
return !!desc.methods.find(method => method.name === methodName);
|
||||
}
|
||||
return false;
|
||||
});
|
||||
},
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a trait from the root actor.
|
||||
|
@ -341,7 +340,7 @@ Target.prototype = {
|
|||
* @param {String} traitName
|
||||
* @return {Mixed}
|
||||
*/
|
||||
getTrait: function(traitName) {
|
||||
getTrait(traitName) {
|
||||
// If the targeted actor exposes traits and has a defined value for this
|
||||
// traits, override the root actor traits
|
||||
if (this.form.traits && traitName in this.form.traits) {
|
||||
|
@ -349,20 +348,20 @@ Target.prototype = {
|
|||
}
|
||||
|
||||
return this.client.traits[traitName];
|
||||
},
|
||||
}
|
||||
|
||||
get tab() {
|
||||
return this._tab;
|
||||
},
|
||||
}
|
||||
|
||||
get form() {
|
||||
return this.activeTab.targetForm;
|
||||
},
|
||||
}
|
||||
|
||||
// Get a promise of the RootActor's form
|
||||
get root() {
|
||||
return this.client.mainRoot.rootForm;
|
||||
},
|
||||
}
|
||||
|
||||
// Temporary fix for bug #1493131 - inspector has a different life cycle
|
||||
// than most other fronts because it is closely related to the toolbox.
|
||||
|
@ -375,7 +374,7 @@ Target.prototype = {
|
|||
this._inspector = await getFront(this.client, "inspector", this.form);
|
||||
this.emit("inspector", this._inspector);
|
||||
return this._inspector;
|
||||
},
|
||||
}
|
||||
|
||||
// Run callback on every front of this type that currently exists, and on every
|
||||
// instantiation of front type in the future.
|
||||
|
@ -385,7 +384,7 @@ Target.prototype = {
|
|||
return callback(front);
|
||||
}
|
||||
return this.on(typeName, callback);
|
||||
},
|
||||
}
|
||||
|
||||
// Get a Front for a target-scoped actor.
|
||||
// i.e. an actor served by RootActor.listTabs or RootActorActor.getTab requests
|
||||
|
@ -402,7 +401,7 @@ Target.prototype = {
|
|||
this.emit(typeName, front);
|
||||
this.fronts.set(typeName, front);
|
||||
return front;
|
||||
},
|
||||
}
|
||||
|
||||
getCachedFront(typeName) {
|
||||
// do not wait for async fronts;
|
||||
|
@ -412,11 +411,11 @@ Target.prototype = {
|
|||
return front;
|
||||
}
|
||||
return null;
|
||||
},
|
||||
}
|
||||
|
||||
get client() {
|
||||
return this._client;
|
||||
},
|
||||
}
|
||||
|
||||
// Tells us if we are debugging content document
|
||||
// or if we are debugging chrome stuff.
|
||||
|
@ -424,45 +423,45 @@ Target.prototype = {
|
|||
// a chrome or a content document.
|
||||
get chrome() {
|
||||
return this._chrome;
|
||||
},
|
||||
}
|
||||
|
||||
// Tells us if the related actor implements BrowsingContextTargetActor
|
||||
// interface and requires to call `attach` request before being used and
|
||||
// `detach` during cleanup.
|
||||
get isBrowsingContext() {
|
||||
return this._isBrowsingContext;
|
||||
},
|
||||
}
|
||||
|
||||
get name() {
|
||||
if (this.isAddon) {
|
||||
return this.form.name;
|
||||
}
|
||||
return this._title;
|
||||
},
|
||||
}
|
||||
|
||||
get url() {
|
||||
return this._url;
|
||||
},
|
||||
}
|
||||
|
||||
get isAddon() {
|
||||
return this.isLegacyAddon || this.isWebExtension;
|
||||
},
|
||||
}
|
||||
|
||||
get isWorkerTarget() {
|
||||
return this.activeTab && this.activeTab.typeName === "workerTarget";
|
||||
},
|
||||
}
|
||||
|
||||
get isLegacyAddon() {
|
||||
return !!(this.form && this.form.actor &&
|
||||
this.form.actor.match(/conn\d+\.addon(Target)?\d+/));
|
||||
},
|
||||
}
|
||||
|
||||
get isWebExtension() {
|
||||
return !!(this.form && this.form.actor && (
|
||||
this.form.actor.match(/conn\d+\.webExtension(Target)?\d+/) ||
|
||||
this.form.actor.match(/child\d+\/webExtension(Target)?\d+/)
|
||||
));
|
||||
},
|
||||
}
|
||||
|
||||
get isContentProcess() {
|
||||
// browser content toolbox's form will be of the form:
|
||||
|
@ -471,25 +470,25 @@ Target.prototype = {
|
|||
// server1.conn0.contentProcessTarget7
|
||||
return !!(this.form && this.form.actor &&
|
||||
this.form.actor.match(/conn\d+\.(content-process\d+\/)?contentProcessTarget\d+/));
|
||||
},
|
||||
}
|
||||
|
||||
get isLocalTab() {
|
||||
return !!this._tab;
|
||||
},
|
||||
}
|
||||
|
||||
get isMultiProcess() {
|
||||
return !this.window;
|
||||
},
|
||||
}
|
||||
|
||||
get canRewind() {
|
||||
return this.activeTab && this.activeTab.traits.canRewind;
|
||||
},
|
||||
}
|
||||
|
||||
isReplayEnabled() {
|
||||
return Services.prefs.getBoolPref("devtools.recordreplay.mvp.enabled")
|
||||
&& this.canRewind
|
||||
&& this.isLocalTab;
|
||||
},
|
||||
}
|
||||
|
||||
getExtensionPathName(url) {
|
||||
// Return the url if the target is not a webextension.
|
||||
|
@ -508,7 +507,7 @@ Target.prototype = {
|
|||
// Return the url if unable to resolve the pathname.
|
||||
return url;
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
/**
|
||||
* For local tabs, returns the tab's contentPrincipal, which can be used as a
|
||||
|
@ -521,7 +520,7 @@ Target.prototype = {
|
|||
return null;
|
||||
}
|
||||
return this.tab.linkedBrowser.contentPrincipal;
|
||||
},
|
||||
}
|
||||
|
||||
/**
|
||||
* Attach the target and its console actor.
|
||||
|
@ -614,32 +613,32 @@ Target.prototype = {
|
|||
})();
|
||||
|
||||
return this._attach;
|
||||
},
|
||||
}
|
||||
|
||||
/**
|
||||
* Listen to the different events.
|
||||
*/
|
||||
_setupListeners: function() {
|
||||
_setupListeners() {
|
||||
this.tab.addEventListener("TabClose", this);
|
||||
this.tab.ownerDocument.defaultView.addEventListener("unload", this);
|
||||
this.tab.addEventListener("TabRemotenessChange", this);
|
||||
},
|
||||
}
|
||||
|
||||
/**
|
||||
* Teardown event listeners.
|
||||
*/
|
||||
_teardownListeners: function() {
|
||||
_teardownListeners() {
|
||||
if (this._tab.ownerDocument.defaultView) {
|
||||
this._tab.ownerDocument.defaultView.removeEventListener("unload", this);
|
||||
}
|
||||
this._tab.removeEventListener("TabClose", this);
|
||||
this._tab.removeEventListener("TabRemotenessChange", this);
|
||||
},
|
||||
}
|
||||
|
||||
/**
|
||||
* Event listener for tabNavigated packet sent by activeTab's front.
|
||||
*/
|
||||
_onTabNavigated: function(packet) {
|
||||
_onTabNavigated(packet) {
|
||||
const event = Object.create(null);
|
||||
event.url = packet.url;
|
||||
event.title = packet.title;
|
||||
|
@ -666,12 +665,12 @@ Target.prototype = {
|
|||
this.emit("navigate", event);
|
||||
this._navWindow = null;
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
/**
|
||||
* Setup listeners for remote debugging, updating existing ones as necessary.
|
||||
*/
|
||||
_setupRemoteListeners: function() {
|
||||
_setupRemoteListeners() {
|
||||
this.client.addListener("closed", this.destroy);
|
||||
|
||||
// For now, only browsing-context inherited actors are using a front,
|
||||
|
@ -700,12 +699,12 @@ Target.prototype = {
|
|||
this.client.addListener("newSource", this._onSourceUpdated);
|
||||
this.client.addListener("updatedSource", this._onSourceUpdated);
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
/**
|
||||
* Teardown listeners for remote debugging.
|
||||
*/
|
||||
_teardownRemoteListeners: function() {
|
||||
_teardownRemoteListeners() {
|
||||
// Remove listeners set in _setupRemoteListeners
|
||||
this.client.removeListener("closed", this.destroy);
|
||||
if (this.activeTab) {
|
||||
|
@ -728,12 +727,12 @@ Target.prototype = {
|
|||
if (this.activeConsole && this._onInspectObject) {
|
||||
this.activeConsole.off("inspectObject", this._onInspectObject);
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle tabs events.
|
||||
*/
|
||||
handleEvent: function(event) {
|
||||
handleEvent(event) {
|
||||
switch (event.type) {
|
||||
case "TabClose":
|
||||
case "unload":
|
||||
|
@ -743,14 +742,14 @@ Target.prototype = {
|
|||
this.onRemotenessChange();
|
||||
break;
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
/**
|
||||
* Automatically respawn the toolbox when the tab changes between being
|
||||
* loaded within the parent process and loaded from a content process.
|
||||
* Process change can go in both ways.
|
||||
*/
|
||||
onRemotenessChange: function() {
|
||||
onRemotenessChange() {
|
||||
// Responsive design do a crazy dance around tabs and triggers
|
||||
// remotenesschange events. But we should ignore them as at the end
|
||||
// the content doesn't change its remoteness.
|
||||
|
@ -771,12 +770,12 @@ Target.prototype = {
|
|||
gDevTools.showToolbox(newTarget);
|
||||
};
|
||||
gDevTools.on("toolbox-destroyed", onToolboxDestroyed);
|
||||
},
|
||||
}
|
||||
|
||||
/**
|
||||
* Target is not alive anymore.
|
||||
*/
|
||||
destroy: function() {
|
||||
destroy() {
|
||||
// If several things call destroy then we give them all the same
|
||||
// destruction promise so we're sure to destroy only once
|
||||
if (this._destroyer) {
|
||||
|
@ -818,12 +817,12 @@ Target.prototype = {
|
|||
})();
|
||||
|
||||
return this._destroyer;
|
||||
},
|
||||
}
|
||||
|
||||
/**
|
||||
* Clean up references to what this target points to.
|
||||
*/
|
||||
_cleanup: function() {
|
||||
_cleanup() {
|
||||
if (this._tab) {
|
||||
targets.delete(this._tab);
|
||||
} else {
|
||||
|
@ -837,12 +836,12 @@ Target.prototype = {
|
|||
this._attach = null;
|
||||
this._title = null;
|
||||
this._url = null;
|
||||
},
|
||||
}
|
||||
|
||||
toString: function() {
|
||||
toString() {
|
||||
const id = this._tab ? this._tab : (this.form && this.form.actor);
|
||||
return `Target:${id}`;
|
||||
},
|
||||
}
|
||||
|
||||
/**
|
||||
* Log an error of some kind to the tab's console.
|
||||
|
@ -852,12 +851,12 @@ Target.prototype = {
|
|||
* @param {String} category
|
||||
* The category of the message. @see nsIScriptError.
|
||||
*/
|
||||
logErrorInPage: function(text, category) {
|
||||
logErrorInPage(text, category) {
|
||||
if (this.activeTab && this.activeTab.traits.logInPage) {
|
||||
const errorFlag = 0;
|
||||
this.activeTab.logInPage({ text, category, flags: errorFlag });
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
/**
|
||||
* Log a warning of some kind to the tab's console.
|
||||
|
@ -867,10 +866,11 @@ Target.prototype = {
|
|||
* @param {String} category
|
||||
* The category of the message. @see nsIScriptError.
|
||||
*/
|
||||
logWarningInPage: function(text, category) {
|
||||
logWarningInPage(text, category) {
|
||||
if (this.activeTab && this.activeTab.traits.logInPage) {
|
||||
const warningFlag = 1;
|
||||
this.activeTab.logInPage({ text, category, flags: warningFlag });
|
||||
}
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
||||
exports.Target = Target;
|
||||
|
|
|
@ -21,6 +21,7 @@ function test() {
|
|||
.then(testSelectTool)
|
||||
.then(testToggleToolboxButtons)
|
||||
.then(testPrefsAreRespectedWhenReopeningToolbox)
|
||||
.then(testButtonStateOnClick)
|
||||
.then(cleanup, errorHandler);
|
||||
});
|
||||
}
|
||||
|
@ -79,6 +80,26 @@ function testPreferenceAndUIStateIsConsistent() {
|
|||
}
|
||||
}
|
||||
|
||||
async function testButtonStateOnClick() {
|
||||
const toolboxButtons = ["#command-button-rulers", "#command-button-measure"];
|
||||
for (const toolboxButton of toolboxButtons) {
|
||||
const button = doc.querySelector(toolboxButton);
|
||||
if (button) {
|
||||
const isChecked = waitUntil(() => button.classList.contains("checked"));
|
||||
|
||||
button.click();
|
||||
await isChecked;
|
||||
ok(button.classList.contains("checked"),
|
||||
`Button for ${toolboxButton} can be toggled on`);
|
||||
|
||||
const isUnchecked = waitUntil(() => !button.classList.contains("checked"));
|
||||
button.click();
|
||||
await isUnchecked;
|
||||
ok(!button.classList.contains("checked"),
|
||||
`Button for ${toolboxButton} can be toggled off`);
|
||||
}
|
||||
}
|
||||
}
|
||||
function testToggleToolboxButtons() {
|
||||
const checkNodes = [...panelWin.document.querySelectorAll(
|
||||
"#enabled-toolbox-buttons-box input[type=checkbox]")];
|
||||
|
|
|
@ -176,7 +176,6 @@ skip-if = verify
|
|||
[browser_markup_screenshot_node.js]
|
||||
[browser_markup_screenshot_node_iframe.js]
|
||||
[browser_markup_screenshot_node_shadowdom.js]
|
||||
skip-if = os == "win" && !debug # skip on Windows opt/pgo platforms Bug 1508435
|
||||
[browser_markup_search_01.js]
|
||||
[browser_markup_shadowdom.js]
|
||||
[browser_markup_shadowdom_clickreveal.js]
|
||||
|
|
|
@ -66,6 +66,13 @@ async function takeNodeScreenshot(inspector) {
|
|||
info("Remove the downloaded screenshot file");
|
||||
await OS.File.remove(filePath);
|
||||
|
||||
// See intermittent Bug 1508435. Even after removing the file, tests still manage to
|
||||
// reuse files from the previous test if they have the same name. Since our file name
|
||||
// is based on a timestamp that has "second" precision, wait for one second to make sure
|
||||
// screenshots will have different names.
|
||||
info("Wait for one second to make sure future screenshots will use a different name");
|
||||
await new Promise(r => setTimeout(r, 1000));
|
||||
|
||||
return image;
|
||||
}
|
||||
/* exported takeNodeScreenshot */
|
||||
|
|
|
@ -565,7 +565,7 @@ var gTests = [
|
|||
{ desc: 'calc() expressions are resolved to the equivalent units',
|
||||
frames: { left: ['calc(10em + 10px)', 'calc(10em + 10%)'] },
|
||||
expected: [ { property: 'left',
|
||||
values: [ valueFormat(0, 'calc(110px)', 'replace', 'linear'),
|
||||
values: [ valueFormat(0, '110px', 'replace', 'linear'),
|
||||
valueFormat(1, 'calc(10% + 100px)', 'replace') ] } ]
|
||||
},
|
||||
|
||||
|
|
|
@ -0,0 +1,145 @@
|
|||
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
||||
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
#include "ScriptableContentIterator.h"
|
||||
#include "nsINode.h"
|
||||
#include "nsRange.h"
|
||||
|
||||
namespace mozilla {
|
||||
|
||||
NS_IMPL_CYCLE_COLLECTING_ADDREF(ScriptableContentIterator)
|
||||
NS_IMPL_CYCLE_COLLECTING_RELEASE(ScriptableContentIterator)
|
||||
|
||||
NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(ScriptableContentIterator)
|
||||
NS_INTERFACE_MAP_ENTRY(nsIScriptableContentIterator)
|
||||
NS_INTERFACE_MAP_ENTRY(nsISupports)
|
||||
NS_INTERFACE_MAP_END
|
||||
|
||||
NS_IMPL_CYCLE_COLLECTION(ScriptableContentIterator, mContentIterator)
|
||||
|
||||
ScriptableContentIterator::ScriptableContentIterator()
|
||||
: mIteratorType(NOT_INITIALIZED) {}
|
||||
|
||||
void ScriptableContentIterator::EnsureContentIterator() {
|
||||
if (mContentIterator) {
|
||||
return;
|
||||
}
|
||||
switch (mIteratorType) {
|
||||
case POST_ORDER_ITERATOR:
|
||||
default:
|
||||
mContentIterator = NS_NewContentIterator();
|
||||
break;
|
||||
case PRE_ORDER_ITERATOR:
|
||||
mContentIterator = NS_NewPreContentIterator();
|
||||
break;
|
||||
case SUBTREE_ITERATOR:
|
||||
mContentIterator = NS_NewContentSubtreeIterator();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
ScriptableContentIterator::InitWithRootNode(IteratorType aType,
|
||||
nsINode* aRoot) {
|
||||
if (aType == NOT_INITIALIZED ||
|
||||
(mIteratorType != NOT_INITIALIZED && aType != mIteratorType)) {
|
||||
return NS_ERROR_INVALID_ARG;
|
||||
}
|
||||
mIteratorType = aType;
|
||||
EnsureContentIterator();
|
||||
return mContentIterator->Init(aRoot);
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
ScriptableContentIterator::InitWithRange(IteratorType aType, nsRange* aRange) {
|
||||
if (aType == NOT_INITIALIZED ||
|
||||
(mIteratorType != NOT_INITIALIZED && aType != mIteratorType)) {
|
||||
return NS_ERROR_INVALID_ARG;
|
||||
}
|
||||
mIteratorType = aType;
|
||||
EnsureContentIterator();
|
||||
return mContentIterator->Init(aRange);
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
ScriptableContentIterator::InitWithPositions(IteratorType aType,
|
||||
nsINode* aStartContainer,
|
||||
uint32_t aStartOffset,
|
||||
nsINode* aEndContainer,
|
||||
uint32_t aEndOffset) {
|
||||
if (aType == NOT_INITIALIZED ||
|
||||
(mIteratorType != NOT_INITIALIZED && aType != mIteratorType)) {
|
||||
return NS_ERROR_INVALID_ARG;
|
||||
}
|
||||
mIteratorType = aType;
|
||||
EnsureContentIterator();
|
||||
return mContentIterator->Init(aStartContainer, aStartOffset, aEndContainer,
|
||||
aEndOffset);
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
ScriptableContentIterator::First() {
|
||||
if (!mContentIterator) {
|
||||
return NS_ERROR_NOT_INITIALIZED;
|
||||
}
|
||||
mContentIterator->First();
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
ScriptableContentIterator::Last() {
|
||||
if (!mContentIterator) {
|
||||
return NS_ERROR_NOT_INITIALIZED;
|
||||
}
|
||||
mContentIterator->Last();
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
ScriptableContentIterator::Next() {
|
||||
if (!mContentIterator) {
|
||||
return NS_ERROR_NOT_INITIALIZED;
|
||||
}
|
||||
mContentIterator->Next();
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
ScriptableContentIterator::Prev() {
|
||||
if (!mContentIterator) {
|
||||
return NS_ERROR_NOT_INITIALIZED;
|
||||
}
|
||||
mContentIterator->Prev();
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
ScriptableContentIterator::GetCurrentNode(nsINode** aNode) {
|
||||
if (!mContentIterator) {
|
||||
return NS_ERROR_NOT_INITIALIZED;
|
||||
}
|
||||
NS_IF_ADDREF(*aNode = mContentIterator->GetCurrentNode());
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
ScriptableContentIterator::GetIsDone(bool* aIsDone) {
|
||||
if (!mContentIterator) {
|
||||
return NS_ERROR_NOT_INITIALIZED;
|
||||
}
|
||||
*aIsDone = mContentIterator->IsDone();
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
ScriptableContentIterator::PositionAt(nsINode* aNode) {
|
||||
if (!mContentIterator) {
|
||||
return NS_ERROR_NOT_INITIALIZED;
|
||||
}
|
||||
return mContentIterator->PositionAt(aNode);
|
||||
}
|
||||
|
||||
} // namespace mozilla
|
|
@ -0,0 +1,34 @@
|
|||
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
||||
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
#ifndef mozilla_scriptablecontentiterator_h
|
||||
#define mozilla_scriptablecontentiterator_h
|
||||
|
||||
#include "mozilla/Attributes.h"
|
||||
#include "nsCOMPtr.h"
|
||||
#include "nsIContentIterator.h"
|
||||
#include "nsIScriptableContentIterator.h"
|
||||
|
||||
namespace mozilla {
|
||||
|
||||
class ScriptableContentIterator final : public nsIScriptableContentIterator {
|
||||
public:
|
||||
ScriptableContentIterator();
|
||||
NS_DECL_CYCLE_COLLECTING_ISUPPORTS
|
||||
NS_DECL_CYCLE_COLLECTION_CLASS(ScriptableContentIterator)
|
||||
NS_DECL_NSISCRIPTABLECONTENTITERATOR
|
||||
|
||||
protected:
|
||||
virtual ~ScriptableContentIterator() = default;
|
||||
void EnsureContentIterator();
|
||||
|
||||
IteratorType mIteratorType;
|
||||
nsCOMPtr<nsIContentIterator> mContentIterator;
|
||||
};
|
||||
|
||||
} // namespace mozilla
|
||||
|
||||
#endif // #ifndef mozilla_scriptablecontentiterator_h
|
|
@ -23,6 +23,7 @@ XPIDL_SOURCES += [
|
|||
'nsIMessageManager.idl',
|
||||
'nsIObjectLoadingContent.idl',
|
||||
'nsIRemoteWindowContext.idl',
|
||||
'nsIScriptableContentIterator.idl',
|
||||
'nsIScriptChannel.idl',
|
||||
'nsISelectionController.idl',
|
||||
'nsISelectionDisplay.idl',
|
||||
|
@ -129,6 +130,7 @@ EXPORTS.mozilla += [
|
|||
'FlushType.h',
|
||||
'FullscreenChange.h',
|
||||
'RangeBoundary.h',
|
||||
'ScriptableContentIterator.h',
|
||||
'SelectionChangeEventDispatcher.h',
|
||||
'TextInputProcessor.h',
|
||||
'UseCounter.h',
|
||||
|
@ -375,6 +377,7 @@ UNIFIED_SOURCES += [
|
|||
'SameProcessMessageQueue.cpp',
|
||||
'ScreenLuminance.cpp',
|
||||
'ScreenOrientation.cpp',
|
||||
'ScriptableContentIterator.cpp',
|
||||
'Selection.cpp',
|
||||
'SelectionChangeEventDispatcher.cpp',
|
||||
'ShadowRoot.cpp',
|
||||
|
|
|
@ -4104,11 +4104,13 @@ nsresult nsContentUtils::DispatchEvent(Document* aDoc, nsISupports* aTarget,
|
|||
// static
|
||||
nsresult nsContentUtils::DispatchInputEvent(Element* aEventTargetElement) {
|
||||
RefPtr<TextEditor> textEditor; // See bug 1506439
|
||||
return DispatchInputEvent(aEventTargetElement, textEditor);
|
||||
return DispatchInputEvent(aEventTargetElement, EditorInputType::eUnknown,
|
||||
textEditor);
|
||||
}
|
||||
|
||||
// static
|
||||
nsresult nsContentUtils::DispatchInputEvent(Element* aEventTargetElement,
|
||||
EditorInputType aEditorInputType,
|
||||
TextEditor* aTextEditor) {
|
||||
if (NS_WARN_IF(!aEventTargetElement)) {
|
||||
return NS_ERROR_INVALID_ARG;
|
||||
|
@ -4142,6 +4144,7 @@ nsresult nsContentUtils::DispatchInputEvent(Element* aEventTargetElement,
|
|||
#endif // #ifdef DEBUG
|
||||
|
||||
if (!useInputEvent) {
|
||||
MOZ_ASSERT(aEditorInputType == EditorInputType::eUnknown);
|
||||
// Dispatch "input" event with Event instance.
|
||||
WidgetEvent widgetEvent(true, eUnidentifiedEvent);
|
||||
widgetEvent.mSpecifiedEventType = nsGkAtoms::oninput;
|
||||
|
@ -4196,6 +4199,8 @@ nsresult nsContentUtils::DispatchInputEvent(Element* aEventTargetElement,
|
|||
inputEvent.mIsComposing =
|
||||
aTextEditor ? !!aTextEditor->GetComposition() : false;
|
||||
|
||||
inputEvent.mInputType = aEditorInputType;
|
||||
|
||||
(new AsyncEventDispatcher(aEventTargetElement, inputEvent))
|
||||
->RunDOMEventWhenSafe();
|
||||
return NS_OK;
|
||||
|
|
|
@ -1397,6 +1397,9 @@ class nsContentUtils {
|
|||
*
|
||||
* @param aEventTarget The event target element of the "input" event.
|
||||
* Must not be nullptr.
|
||||
* @param aEditorInputType The inputType value of InputEvent.
|
||||
* If aEventTarget won't dispatch "input" event
|
||||
* with InputEvent, set EditorInputType::eUnknown.
|
||||
* @param aTextEditor Optional. If this is called by editor,
|
||||
* editor should set this. Otherwise, leave
|
||||
* nullptr.
|
||||
|
@ -1405,6 +1408,7 @@ class nsContentUtils {
|
|||
static nsresult DispatchInputEvent(Element* aEventTarget);
|
||||
MOZ_CAN_RUN_SCRIPT
|
||||
static nsresult DispatchInputEvent(Element* aEventTarget,
|
||||
mozilla::EditorInputType aEditorInputType,
|
||||
mozilla::TextEditor* aTextEditor);
|
||||
|
||||
/**
|
||||
|
|
|
@ -0,0 +1,74 @@
|
|||
/* -*- Mode: IDL; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
#include "nsISupports.idl"
|
||||
|
||||
webidl Node;
|
||||
webidl Range;
|
||||
|
||||
/**
|
||||
* nsIScriptableContentIterator is designed to testing concrete classes of
|
||||
* nsIContentIterator.
|
||||
*/
|
||||
[scriptable, builtinclass, uuid(9f25fb2a-265f-44f9-a122-62bbf443239e)]
|
||||
interface nsIScriptableContentIterator : nsISupports
|
||||
{
|
||||
cenum IteratorType : 8 {
|
||||
NOT_INITIALIZED,
|
||||
POST_ORDER_ITERATOR,
|
||||
PRE_ORDER_ITERATOR,
|
||||
SUBTREE_ITERATOR
|
||||
};
|
||||
|
||||
/**
|
||||
* You need to call initWith*() first. Then, the instance of this interface
|
||||
* decides the type of iterator with its aType argument. You can call
|
||||
* initWith*() multiple times, but you need to keep setting same type as
|
||||
* previous call. If you set different type, these method with throw an
|
||||
* exception.
|
||||
*/
|
||||
|
||||
// See nsIContentIterator::Init(nsINode*)
|
||||
void initWithRootNode(in nsIScriptableContentIterator_IteratorType aType,
|
||||
in Node aRoot);
|
||||
|
||||
// See nsIContentIterator::Init(nsRange*)
|
||||
void initWithRange(in nsIScriptableContentIterator_IteratorType aType,
|
||||
in Range aRange);
|
||||
|
||||
// See nsIContentIterator::Init(nsINode*, uint32_t, nsINode*, uint32_t)
|
||||
void initWithPositions(in nsIScriptableContentIterator_IteratorType aType,
|
||||
in Node aStartContainer, in unsigned long aStartOffset,
|
||||
in Node aEndContainer, in unsigned long aEndOffset);
|
||||
|
||||
// See nsIContentIterator::First()
|
||||
void first();
|
||||
|
||||
// See nsIContentIterator::Last()
|
||||
void last();
|
||||
|
||||
// See nsIContentIterator::Next()
|
||||
void next();
|
||||
|
||||
// See nsIContentIterator::Prev()
|
||||
void prev();
|
||||
|
||||
// See nsIContentIterator::GetCurrentNode()
|
||||
readonly attribute Node currentNode;
|
||||
|
||||
// See nsIContentIterator::IsDone()
|
||||
readonly attribute bool isDone;
|
||||
|
||||
// See nsIContentIterator::PositionAt(nsINode*)
|
||||
void positionAt(in Node aNode);
|
||||
};
|
||||
|
||||
%{C++
|
||||
#define SCRIPTABLE_CONTENT_ITERATOR_CID \
|
||||
{ 0xf68037ec, 0x2790, 0x44c5, \
|
||||
{ 0x8e, 0x5f, 0xdf, 0x5d, 0xa5, 0x8b, 0x93, 0xa7 } }
|
||||
#define SCRIPTABLE_CONTENT_ITERATOR_CONTRACTID \
|
||||
"@mozilla.org/scriptable-content-iterator;1"
|
||||
%}
|
|
@ -59,7 +59,7 @@ function onunload()
|
|||
SimpleTest.finish();
|
||||
}
|
||||
|
||||
function checkInputEvent(aEvent, aIsComposing, aDescription) {
|
||||
function checkInputEvent(aEvent, aIsComposing, aInputType, aDescription) {
|
||||
if (aEvent.type != "input") {
|
||||
return;
|
||||
}
|
||||
|
@ -67,6 +67,7 @@ function checkInputEvent(aEvent, aIsComposing, aDescription) {
|
|||
is(aEvent.cancelable, false, `${aDescription}"input" event should be never cancelable`);
|
||||
is(aEvent.bubbles, true, `${aDescription}"input" event should always bubble`);
|
||||
is(aEvent.isComposing, aIsComposing, `${aDescription}isComposing should be ${aIsComposing}`);
|
||||
is(aEvent.inputType, aInputType, `${aDescription}inputType should be "${aInputType}"`);
|
||||
}
|
||||
|
||||
const kIsMac = (navigator.platform.indexOf("Mac") == 0);
|
||||
|
@ -232,7 +233,7 @@ function runBeginInputTransactionMethodTests()
|
|||
description + "events[2] should be text");
|
||||
is(events[3].type, "input",
|
||||
description + "events[3] should be input");
|
||||
checkInputEvent(events[3], true, description);
|
||||
checkInputEvent(events[3], true, "insertCompositionText", description);
|
||||
TIP1.cancelComposition();
|
||||
|
||||
// Let's check if beginInputTransaction() fails to steal the rights of TextEventDispatcher during commitComposition().
|
||||
|
@ -268,7 +269,7 @@ function runBeginInputTransactionMethodTests()
|
|||
description + "events[1] should be compositionend");
|
||||
is(events[2].type, "input",
|
||||
description + "events[2] should be input");
|
||||
checkInputEvent(events[2], false, description);
|
||||
checkInputEvent(events[2], false, "insertCompositionText", description);
|
||||
|
||||
// Let's check if beginInputTransaction() fails to steal the rights of TextEventDispatcher during commitCompositionWith("bar").
|
||||
events = [];
|
||||
|
@ -316,7 +317,7 @@ function runBeginInputTransactionMethodTests()
|
|||
description + "events[3] should be compositionend");
|
||||
is(events[4].type, "input",
|
||||
description + "events[4] should be input");
|
||||
checkInputEvent(events[4], false, description);
|
||||
checkInputEvent(events[4], false, "insertCompositionText", description);
|
||||
|
||||
// Let's check if beginInputTransaction() fails to steal the rights of TextEventDispatcher during cancelComposition().
|
||||
events = [];
|
||||
|
@ -359,7 +360,7 @@ function runBeginInputTransactionMethodTests()
|
|||
description + "events[2] should be compositionend");
|
||||
is(events[3].type, "input",
|
||||
description + "events[3] should be input");
|
||||
checkInputEvent(events[3], false, description);
|
||||
checkInputEvent(events[3], false, "insertCompositionText", description);
|
||||
|
||||
// Let's check if beginInputTransaction() fails to steal the rights of TextEventDispatcher during keydown() and keyup().
|
||||
events = [];
|
||||
|
@ -399,7 +400,7 @@ function runBeginInputTransactionMethodTests()
|
|||
description + "events[1] should be keypress");
|
||||
is(events[2].type, "input",
|
||||
description + "events[2] should be input");
|
||||
checkInputEvent(events[2], false, description);
|
||||
checkInputEvent(events[2], false, "insertText", description);
|
||||
is(events[3].type, "keyup",
|
||||
description + "events[3] should be keyup");
|
||||
|
||||
|
@ -457,7 +458,7 @@ function runBeginInputTransactionMethodTests()
|
|||
description + "events[2] should be text");
|
||||
is(events[3].type, "input",
|
||||
description + "events[3] should be input");
|
||||
checkInputEvent(events[3], true, description);
|
||||
checkInputEvent(events[3], true, "insertCompositionText", description);
|
||||
TIP1.cancelComposition();
|
||||
|
||||
// Let's check if beginInputTransactionForTests() fails to steal the rights of TextEventDispatcher during commitComposition().
|
||||
|
@ -493,7 +494,7 @@ function runBeginInputTransactionMethodTests()
|
|||
description + "events[1] should be compositionend");
|
||||
is(events[2].type, "input",
|
||||
description + "events[2] should be input");
|
||||
checkInputEvent(events[2], false, description);
|
||||
checkInputEvent(events[2], false, "insertCompositionText", description);
|
||||
|
||||
// Let's check if beginInputTransactionForTests() fails to steal the rights of TextEventDispatcher during commitCompositionWith("bar").
|
||||
events = [];
|
||||
|
@ -541,7 +542,7 @@ function runBeginInputTransactionMethodTests()
|
|||
description + "events[3] should be compositionend");
|
||||
is(events[4].type, "input",
|
||||
description + "events[4] should be input");
|
||||
checkInputEvent(events[4], false, description);
|
||||
checkInputEvent(events[4], false, "insertCompositionText", description);
|
||||
|
||||
// Let's check if beginInputTransactionForTests() fails to steal the rights of TextEventDispatcher during cancelComposition().
|
||||
events = [];
|
||||
|
@ -584,7 +585,7 @@ function runBeginInputTransactionMethodTests()
|
|||
description + "events[2] should be compositionend");
|
||||
is(events[3].type, "input",
|
||||
description + "events[3] should be input");
|
||||
checkInputEvent(events[3], false, description);
|
||||
checkInputEvent(events[3], false, "insertCompositionText", description);
|
||||
|
||||
// Let's check if beginInputTransactionForTests() fails to steal the rights of TextEventDispatcher during keydown() and keyup().
|
||||
events = [];
|
||||
|
@ -624,7 +625,7 @@ function runBeginInputTransactionMethodTests()
|
|||
description + "events[1] should be keypress");
|
||||
is(events[2].type, "input",
|
||||
description + "events[2] should be input");
|
||||
checkInputEvent(events[2], false, description);
|
||||
checkInputEvent(events[2], false, "insertText", description);
|
||||
is(events[3].type, "keyup",
|
||||
description + "events[3] should be keyup");
|
||||
|
||||
|
@ -712,7 +713,7 @@ function runBeginInputTransactionMethodTests()
|
|||
description + "events[2] should be text");
|
||||
is(events[3].type, "input",
|
||||
description + "events[3] should be input");
|
||||
checkInputEvent(events[3], true, description);
|
||||
checkInputEvent(events[3], true, "insertCompositionText", description);
|
||||
TIP1.cancelComposition();
|
||||
|
||||
// Let's check if beginInputTransaction() with another window fails to begin new input transaction with different TextEventDispatcher during commitComposition().
|
||||
|
@ -766,7 +767,7 @@ function runBeginInputTransactionMethodTests()
|
|||
description + "events[1] should be compositionend");
|
||||
is(events[2].type, "input",
|
||||
description + "events[2] should be input");
|
||||
checkInputEvent(events[2], false, description);
|
||||
checkInputEvent(events[2], false, "insertCompositionText", description);
|
||||
|
||||
// Let's check if beginInputTransaction() with another window fails to begin new input transaction with different TextEventDispatcher during commitCompositionWith("bar");.
|
||||
events = [];
|
||||
|
@ -844,7 +845,7 @@ function runBeginInputTransactionMethodTests()
|
|||
description + "events[3] should be compositionend");
|
||||
is(events[4].type, "input",
|
||||
description + "events[4] should be input");
|
||||
checkInputEvent(events[4], false, description);
|
||||
checkInputEvent(events[4], false, "insertCompositionText", description);
|
||||
|
||||
// Let's check if beginInputTransaction() with another window fails to begin new input transaction with different TextEventDispatcher during cancelComposition();.
|
||||
events = [];
|
||||
|
@ -911,7 +912,7 @@ function runBeginInputTransactionMethodTests()
|
|||
description + "events[2] should be compositionend");
|
||||
is(events[3].type, "input",
|
||||
description + "events[3] should be input");
|
||||
checkInputEvent(events[3], false, description);
|
||||
checkInputEvent(events[3], false, "insertCompositionText", description);
|
||||
|
||||
// Let's check if beginInputTransaction() with another window fails to begin new input transaction with different TextEventDispatcher during keydown() and keyup();.
|
||||
events = [];
|
||||
|
@ -975,7 +976,7 @@ function runBeginInputTransactionMethodTests()
|
|||
description + "events[1] should be keypress");
|
||||
is(events[2].type, "input",
|
||||
description + "events[2] should be input");
|
||||
checkInputEvent(events[2], false, description);
|
||||
checkInputEvent(events[2], false, "insertText", description);
|
||||
is(events[3].type, "keyup",
|
||||
description + "events[3] should be keyup");
|
||||
|
||||
|
@ -1063,7 +1064,7 @@ function runBeginInputTransactionMethodTests()
|
|||
description + "events[2] should be text");
|
||||
is(events[3].type, "input",
|
||||
description + "events[3] should be input");
|
||||
checkInputEvent(events[3], true, description);
|
||||
checkInputEvent(events[3], true, "insertCompositionText", description);
|
||||
TIP1.cancelComposition();
|
||||
|
||||
// Let's check if beginInputTransactionForTests() with another window fails to begin new input transaction with different TextEventDispatcher during commitComposition().
|
||||
|
@ -1117,7 +1118,7 @@ function runBeginInputTransactionMethodTests()
|
|||
description + "events[1] should be compositionend");
|
||||
is(events[2].type, "input",
|
||||
description + "events[2] should be input");
|
||||
checkInputEvent(events[2], false, description);
|
||||
checkInputEvent(events[2], false, "insertCompositionText", description);
|
||||
|
||||
// Let's check if beginInputTransactionForTests() with another window fails to begin new input transaction with different TextEventDispatcher during commitCompositionWith("bar");.
|
||||
events = [];
|
||||
|
@ -1195,7 +1196,7 @@ function runBeginInputTransactionMethodTests()
|
|||
description + "events[3] should be compositionend");
|
||||
is(events[4].type, "input",
|
||||
description + "events[4] should be input");
|
||||
checkInputEvent(events[4], false, description);
|
||||
checkInputEvent(events[4], false, "insertCompositionText", description);
|
||||
|
||||
// Let's check if beginInputTransactionForTests() with another window fails to begin new input transaction with different TextEventDispatcher during cancelComposition();.
|
||||
events = [];
|
||||
|
@ -1262,7 +1263,7 @@ function runBeginInputTransactionMethodTests()
|
|||
description + "events[2] should be compositionend");
|
||||
is(events[3].type, "input",
|
||||
description + "events[3] should be input");
|
||||
checkInputEvent(events[3], false, description);
|
||||
checkInputEvent(events[3], false, "insertCompositionText", description);
|
||||
|
||||
// Let's check if beginInputTransactionForTests() with another window fails to begin new input transaction with different TextEventDispatcher during keydown() and keyup();.
|
||||
events = [];
|
||||
|
@ -1326,7 +1327,7 @@ function runBeginInputTransactionMethodTests()
|
|||
description + "events[1] should be keypress");
|
||||
is(events[2].type, "input",
|
||||
description + "events[2] should be input");
|
||||
checkInputEvent(events[2], false, description);
|
||||
checkInputEvent(events[2], false, "insertText", description);
|
||||
is(events[3].type, "keyup",
|
||||
description + "events[3] should be keyup");
|
||||
|
||||
|
|
|
@ -622,6 +622,9 @@ skip-if = toolkit == 'android' # Timeouts on android due to page closing issues
|
|||
[test_clearTimeoutIntervalNoArg.html]
|
||||
[test_constructor-assignment.html]
|
||||
[test_constructor.html]
|
||||
[test_content_iterator_post_order.html]
|
||||
[test_content_iterator_pre_order.html]
|
||||
[test_content_iterator_subtree.html]
|
||||
[test_copyimage.html]
|
||||
subsuite = clipboard
|
||||
skip-if = toolkit == 'android' #bug 904183
|
||||
|
|
|
@ -0,0 +1,875 @@
|
|||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>Test for post-order content iterator</title>
|
||||
<script src="/tests/SimpleTest/SimpleTest.js"></script>
|
||||
<link rel="stylesheet" href="/tests/SimpleTest/test.css">
|
||||
<script>
|
||||
var Cc = SpecialPowers.Cc;
|
||||
var Ci = SpecialPowers.Ci;
|
||||
function finish() {
|
||||
// The SimpleTest may require usual elements in the template, but they shouldn't be during test.
|
||||
// So, let's create them at end of the test.
|
||||
document.body.innerHTML = '<div id="display"></div><div id="content"></div><pre id="test"></pre>';
|
||||
SimpleTest.finish();
|
||||
}
|
||||
|
||||
function createContentIterator() {
|
||||
return Cc["@mozilla.org/scriptable-content-iterator;1"]
|
||||
.createInstance(Ci.nsIScriptableContentIterator);
|
||||
}
|
||||
|
||||
function getNodeDescription(aNode) {
|
||||
if (aNode === undefined) {
|
||||
return "undefine";
|
||||
}
|
||||
if (aNode === null) {
|
||||
return "null";
|
||||
}
|
||||
function getElementDescription(aElement) {
|
||||
if (aElement.tagName === "BR") {
|
||||
if (aElement.previousSibling) {
|
||||
return `<br> element after ${getNodeDescription(aElement.previousSibling)}`;
|
||||
}
|
||||
return `<br> element in ${getElementDescription(aElement.parentElement)}`;
|
||||
}
|
||||
let hasHint = aElement == document.body;
|
||||
let tag = `<${aElement.tagName.toLowerCase()}`;
|
||||
if (aElement.getAttribute("id")) {
|
||||
tag += ` id="${aElement.getAttribute("id")}"`;
|
||||
hasHint = true;
|
||||
}
|
||||
if (aElement.getAttribute("class")) {
|
||||
tag += ` class="${aElement.getAttribute("class")}"`;
|
||||
hasHint = true;
|
||||
}
|
||||
if (aElement.getAttribute("type")) {
|
||||
tag += ` type="${aElement.getAttribute("type")}"`;
|
||||
}
|
||||
if (aElement.getAttribute("name")) {
|
||||
tag += ` name="${aElement.getAttribute("name")}"`;
|
||||
}
|
||||
if (aElement.getAttribute("value")) {
|
||||
tag += ` value="${aElement.getAttribute("value")}"`;
|
||||
hasHint = true;
|
||||
}
|
||||
if (aElement.getAttribute("style")) {
|
||||
tag += ` style="${aElement.getAttribute("style")}"`;
|
||||
hasHint = true;
|
||||
}
|
||||
if (hasHint) {
|
||||
return tag + ">";
|
||||
}
|
||||
return `${tag}> in ${getElementDescription(aElement.parentElement)}`;
|
||||
}
|
||||
switch (aNode.nodeType) {
|
||||
case aNode.TEXT_NODE:
|
||||
return `text node, "${aNode.wholeText.replace(/\n/g, '\\n')}"`;
|
||||
case aNode.COMMENT_NODE:
|
||||
return `comment node, "${aNode.data.replace(/\n/g, '\\n')}"`;
|
||||
case aNode.ELEMENT_NODE:
|
||||
return getElementDescription(SpecialPowers.unwrap(aNode));
|
||||
default:
|
||||
return "unknown node";
|
||||
}
|
||||
}
|
||||
|
||||
SimpleTest.waitForExplicitFinish();
|
||||
SimpleTest.waitForFocus(function () {
|
||||
let iter = createContentIterator();
|
||||
|
||||
/**
|
||||
* Basic behavior tests of first(), last(), prev() and next() after initialized with an empty element.
|
||||
*/
|
||||
document.body.innerHTML = "<div></div>";
|
||||
let description = "Initialized with empty <div> as root node:";
|
||||
iter.initWithRootNode(Ci.nsIScriptableContentIterator.POST_ORDER_ITERATOR, document.body.firstChild);
|
||||
is(SpecialPowers.unwrap(iter.currentNode), document.body.firstChild,
|
||||
`${description} currentNode should be the <div> immediately after initialization (got: ${getNodeDescription(iter.currentNode)})`);
|
||||
ok(!iter.isDone, `${description} isDone shouldn't be true immediately after initialization`);
|
||||
|
||||
iter.first();
|
||||
is(SpecialPowers.unwrap(iter.currentNode), document.body.firstChild,
|
||||
`${description} currentNode should be the <div> after calling first() (got: ${getNodeDescription(iter.currentNode)})`);
|
||||
ok(!iter.isDone, `${description} isDone shouldn't be true after calling first()`);
|
||||
|
||||
iter.last();
|
||||
is(SpecialPowers.unwrap(iter.currentNode), document.body.firstChild,
|
||||
`${description} currentNode should be the <div> after calling last() (got: ${getNodeDescription(iter.currentNode)})`);
|
||||
ok(!iter.isDone, `${description} isDone shouldn't be true after calling last()`);
|
||||
|
||||
iter.prev();
|
||||
is(SpecialPowers.unwrap(iter.currentNode), null,
|
||||
`${description} currentNode should be null after calling prev() (got: ${getNodeDescription(iter.currentNode)})`);
|
||||
ok(iter.isDone, `${description} isDone should be true after calling prev()`); // XXX Is this expected?
|
||||
|
||||
iter.first();
|
||||
is(SpecialPowers.unwrap(iter.currentNode), document.body.firstChild,
|
||||
`${description} currentNode should be the <div> after calling first() even after once done (got: ${getNodeDescription(iter.currentNode)})`);
|
||||
ok(!iter.isDone, `${description} isDone shouldn't be true after calling first() even after once done`);
|
||||
|
||||
iter.next();
|
||||
is(SpecialPowers.unwrap(iter.currentNode), null,
|
||||
`${description} currentNode should be null after calling next() (got: ${getNodeDescription(iter.currentNode)})`);
|
||||
ok(iter.isDone, `${description} isDone should be true after calling next()`);
|
||||
|
||||
/**
|
||||
* Basic behavior tests of first(), last(), prev() and next() after initialized with a range which selects empty element.
|
||||
*/
|
||||
let range = document.createRange();
|
||||
range.selectNode(document.body.firstChild);
|
||||
description = "Initialized with range including only empty <div>:";
|
||||
iter.initWithRange(Ci.nsIScriptableContentIterator.POST_ORDER_ITERATOR, range);
|
||||
is(SpecialPowers.unwrap(iter.currentNode), document.body.firstChild,
|
||||
`${description} currentNode should be the <div> immediately after initialization (got: ${getNodeDescription(iter.currentNode)})`);
|
||||
ok(!iter.isDone, `${description} isDone shouldn't be true immediately after initialization`);
|
||||
|
||||
iter.first();
|
||||
is(SpecialPowers.unwrap(iter.currentNode), document.body.firstChild,
|
||||
`${description} currentNode should be the <div> after calling first() (got: ${getNodeDescription(iter.currentNode)})`);
|
||||
ok(!iter.isDone, `${description} isDone shouldn't be true after calling first()`);
|
||||
|
||||
iter.last();
|
||||
is(SpecialPowers.unwrap(iter.currentNode), document.body.firstChild,
|
||||
`${description} currentNode should be the <div> after calling last() (got: ${getNodeDescription(iter.currentNode)})`);
|
||||
ok(!iter.isDone, `${description} isDone shouldn't be true after calling last()`);
|
||||
|
||||
iter.prev();
|
||||
is(SpecialPowers.unwrap(iter.currentNode), null,
|
||||
`${description} currentNode should be null after calling prev() (got: ${getNodeDescription(iter.currentNode)})`);
|
||||
ok(iter.isDone, `${description} isDone should be true after calling prev()`); // XXX Is this expected?
|
||||
|
||||
iter.first();
|
||||
is(SpecialPowers.unwrap(iter.currentNode), document.body.firstChild,
|
||||
`${description} currentNode should be the <div> after calling first() even after once done (got: ${getNodeDescription(iter.currentNode)})`);
|
||||
ok(!iter.isDone, `${description} isDone shouldn't be true after calling first() even after once done`);
|
||||
|
||||
iter.next();
|
||||
is(SpecialPowers.unwrap(iter.currentNode), null,
|
||||
`${description} currentNode should be null after calling next() (got: ${getNodeDescription(iter.currentNode)})`);
|
||||
ok(iter.isDone, `${description} isDone should be true after calling next()`);
|
||||
|
||||
/**
|
||||
* Basic behavior tests of first(), last(), prev() and next() after initialized with positions which select empty element.
|
||||
*/
|
||||
range.selectNode(document.body.firstChild);
|
||||
description = "Initialized with positions including only empty <div>:";
|
||||
iter.initWithPositions(Ci.nsIScriptableContentIterator.POST_ORDER_ITERATOR,
|
||||
range.startContainer, range.startOffset,
|
||||
range.endContainer, range.endOffset);
|
||||
is(SpecialPowers.unwrap(iter.currentNode), document.body.firstChild,
|
||||
`${description} currentNode should be the <div> immediately after initialization (got: ${getNodeDescription(iter.currentNode)})`);
|
||||
ok(!iter.isDone, `${description} isDone shouldn't be true immediately after initialization`);
|
||||
|
||||
iter.first();
|
||||
is(SpecialPowers.unwrap(iter.currentNode), document.body.firstChild,
|
||||
`${description} currentNode should be the <div> after calling first() (got: ${getNodeDescription(iter.currentNode)})`);
|
||||
ok(!iter.isDone, `${description} isDone shouldn't be true after calling first()`);
|
||||
|
||||
iter.last();
|
||||
is(SpecialPowers.unwrap(iter.currentNode), document.body.firstChild,
|
||||
`${description} currentNode should be the <div> after calling last() (got: ${getNodeDescription(iter.currentNode)})`);
|
||||
ok(!iter.isDone, `${description} isDone shouldn't be true after calling last()`);
|
||||
|
||||
iter.prev();
|
||||
is(SpecialPowers.unwrap(iter.currentNode), null,
|
||||
`${description} currentNode should be null after calling prev() (got: ${getNodeDescription(iter.currentNode)})`);
|
||||
ok(iter.isDone, `${description} isDone should be true after calling prev()`); // XXX Is this expected?
|
||||
|
||||
iter.first();
|
||||
is(SpecialPowers.unwrap(iter.currentNode), document.body.firstChild,
|
||||
`${description} currentNode should be the <div> after calling first() even after once done (got: ${getNodeDescription(iter.currentNode)})`);
|
||||
ok(!iter.isDone, `${description} isDone shouldn't be true after calling first() even after once done`);
|
||||
|
||||
iter.next();
|
||||
is(SpecialPowers.unwrap(iter.currentNode), null,
|
||||
`${description} currentNode should be null after calling next() (got: ${getNodeDescription(iter.currentNode)})`);
|
||||
ok(iter.isDone, `${description} isDone should be true after calling next()`);
|
||||
|
||||
/**
|
||||
* Tests to initializing with collapsed range in an empty element.
|
||||
*/
|
||||
range = document.createRange();
|
||||
range.collapse(document.body.firstChild, 0);
|
||||
description = "Initialized with range collapsed in empty <div>:";
|
||||
iter.initWithRange(Ci.nsIScriptableContentIterator.POST_ORDER_ITERATOR, range);
|
||||
is(SpecialPowers.unwrap(iter.currentNode), null,
|
||||
`${description} currentNode should be null immediately after initialization (got: ${getNodeDescription(iter.currentNode)})`);
|
||||
ok(iter.isDone, `${description} isDone should be true immediately after initialization`);
|
||||
|
||||
iter.first();
|
||||
is(SpecialPowers.unwrap(iter.currentNode), null,
|
||||
`${description} currentNode should be null even after calling first() (got: ${getNodeDescription(iter.currentNode)})`);
|
||||
ok(iter.isDone, `${description} isDone should be true even after calling first()`);
|
||||
|
||||
/**
|
||||
* Tests to initializing with collapsed range in an empty element.
|
||||
*/
|
||||
description = "Initialized with a position in empty <div>:";
|
||||
iter.initWithPositions(Ci.nsIScriptableContentIterator.POST_ORDER_ITERATOR,
|
||||
document.body.firstChild, 0, document.body.firstChild, 0);
|
||||
is(SpecialPowers.unwrap(iter.currentNode), null,
|
||||
`${description} currentNode should be null immediately after initialization (got: ${getNodeDescription(iter.currentNode)})`);
|
||||
ok(iter.isDone, `${description} isDone should be true immediately after initialization`);
|
||||
|
||||
iter.first();
|
||||
is(SpecialPowers.unwrap(iter.currentNode), null,
|
||||
`${description} currentNode should be null even after calling first() (got: ${getNodeDescription(iter.currentNode)})`);
|
||||
ok(iter.isDone, `${description} isDone should be true even after calling first()`);
|
||||
|
||||
/**
|
||||
* Basic behavior tests of first(), last(), prev() and next() after initialized with the text element.
|
||||
*/
|
||||
document.body.innerHTML = "<div>some text.</div>";
|
||||
description = "Initialized with a text node as root node:";
|
||||
iter.initWithRootNode(Ci.nsIScriptableContentIterator.POST_ORDER_ITERATOR, document.body.firstChild.firstChild);
|
||||
is(SpecialPowers.unwrap(iter.currentNode), document.body.firstChild.firstChild,
|
||||
`${description} currentNode should be the text node immediately after initialization (got: ${getNodeDescription(iter.currentNode)})`);
|
||||
ok(!iter.isDone, `${description} isDone shouldn't be true immediately after initialization`);
|
||||
|
||||
iter.first();
|
||||
is(SpecialPowers.unwrap(iter.currentNode), document.body.firstChild.firstChild,
|
||||
`${description} currentNode should be the text node after calling first() (got: ${getNodeDescription(iter.currentNode)})`);
|
||||
ok(!iter.isDone, `${description} isDone shouldn't be the text node after calling first()`);
|
||||
|
||||
iter.last();
|
||||
is(SpecialPowers.unwrap(iter.currentNode), document.body.firstChild.firstChild,
|
||||
`${description} currentNode should be the text node after calling last() (got: ${getNodeDescription(iter.currentNode)})`);
|
||||
ok(!iter.isDone, `${description} isDone shouldn't be true after calling last()`);
|
||||
|
||||
iter.prev();
|
||||
is(SpecialPowers.unwrap(iter.currentNode), null,
|
||||
`${description} currentNode should be null after calling prev() (got: ${getNodeDescription(iter.currentNode)})`);
|
||||
ok(iter.isDone, `${description} isDone should be true after calling prev()`); // XXX Is this expected?
|
||||
|
||||
iter.first();
|
||||
is(SpecialPowers.unwrap(iter.currentNode), document.body.firstChild.firstChild,
|
||||
`${description} currentNode should be the text node after calling first() even after once done (got: ${getNodeDescription(iter.currentNode)})`);
|
||||
ok(!iter.isDone, `${description} isDone shouldn't be true after calling first() even after once done`);
|
||||
|
||||
iter.next();
|
||||
is(SpecialPowers.unwrap(iter.currentNode), null,
|
||||
`${description} currentNode should be null after calling next() (got: ${getNodeDescription(iter.currentNode)})`);
|
||||
ok(iter.isDone, `${description} isDone should be true after calling next()`);
|
||||
|
||||
/**
|
||||
* Basic behavior tests of first(), last(), prev() and next() after initialized with a range which selects the text node.
|
||||
*/
|
||||
range = document.createRange();
|
||||
range.selectNode(document.body.firstChild.firstChild);
|
||||
description = "Initialized with range including only text node:";
|
||||
iter.initWithRange(Ci.nsIScriptableContentIterator.POST_ORDER_ITERATOR, range);
|
||||
is(SpecialPowers.unwrap(iter.currentNode), document.body.firstChild.firstChild,
|
||||
`${description} currentNode should be the text node immediately after initialization (got: ${getNodeDescription(iter.currentNode)})`);
|
||||
ok(!iter.isDone, `${description} isDone shouldn't be true immediately after initialization`);
|
||||
|
||||
iter.first();
|
||||
is(SpecialPowers.unwrap(iter.currentNode), document.body.firstChild.firstChild,
|
||||
`${description} currentNode should be the text node after calling first() (got: ${getNodeDescription(iter.currentNode)})`);
|
||||
ok(!iter.isDone, `${description} isDone shouldn't be true after calling first()`);
|
||||
|
||||
iter.last();
|
||||
is(SpecialPowers.unwrap(iter.currentNode), document.body.firstChild.firstChild,
|
||||
`${description} currentNode should be the text node after calling last() (got: ${getNodeDescription(iter.currentNode)})`);
|
||||
ok(!iter.isDone, `${description} isDone shouldn't be true after calling last()`);
|
||||
|
||||
iter.prev();
|
||||
is(SpecialPowers.unwrap(iter.currentNode), null,
|
||||
`${description} currentNode should be null after calling prev() (got: ${getNodeDescription(iter.currentNode)})`);
|
||||
ok(iter.isDone, `${description} isDone should be true after calling prev()`); // XXX Is this expected?
|
||||
|
||||
iter.first();
|
||||
is(SpecialPowers.unwrap(iter.currentNode), document.body.firstChild.firstChild,
|
||||
`${description} currentNode should be the text node after calling first() even after once done (got: ${getNodeDescription(iter.currentNode)})`);
|
||||
ok(!iter.isDone, `${description} isDone shouldn't be true after calling first() even after once done`);
|
||||
|
||||
iter.next();
|
||||
is(SpecialPowers.unwrap(iter.currentNode), null,
|
||||
`${description} currentNode should be null after calling next() (got: ${getNodeDescription(iter.currentNode)})`);
|
||||
ok(iter.isDone, `${description} isDone should be true after calling next()`);
|
||||
|
||||
/**
|
||||
* Basic behavior tests of first() and next() after initialized with positions which select the text node.
|
||||
* XXX In this case, content iterator lists up the parent <div> element. Not sure if this is intentional difference
|
||||
* from initWithRange().
|
||||
*/
|
||||
range.selectNode(document.body.firstChild);
|
||||
description = "Initialized with positions including only text node:";
|
||||
iter.initWithPositions(Ci.nsIScriptableContentIterator.POST_ORDER_ITERATOR,
|
||||
range.startContainer, range.startOffset,
|
||||
range.endContainer, range.endOffset);
|
||||
is(SpecialPowers.unwrap(iter.currentNode), document.body.firstChild.firstChild,
|
||||
`${description} currentNode should be the text node immediately after initialization (got: ${getNodeDescription(iter.currentNode)})`);
|
||||
ok(!iter.isDone, `${description} isDone shouldn't be true immediately after initialization`);
|
||||
|
||||
iter.first();
|
||||
is(SpecialPowers.unwrap(iter.currentNode), document.body.firstChild.firstChild,
|
||||
`${description} currentNode should be the text node after calling first() (got: ${getNodeDescription(iter.currentNode)})`);
|
||||
ok(!iter.isDone, `${description} isDone shouldn't be true after calling first()`);
|
||||
|
||||
iter.next();
|
||||
is(SpecialPowers.unwrap(iter.currentNode), document.body.firstChild,
|
||||
`${description} currentNode should be the <div> element after calling next() from first position (got: ${getNodeDescription(iter.currentNode)})`);
|
||||
ok(!iter.isDone, `${description} isDone shouldn't be true after calling next() from first position`);
|
||||
|
||||
iter.next();
|
||||
is(SpecialPowers.unwrap(iter.currentNode), null,
|
||||
`${description} currentNode should be null after calling next() from second position (got: ${getNodeDescription(iter.currentNode)})`);
|
||||
ok(iter.isDone, `${description} isDone should be true after calling next() from second position`);
|
||||
|
||||
/**
|
||||
* Tests to initializing with collapsed range at start of a text node.
|
||||
*/
|
||||
range = document.createRange();
|
||||
range.collapse(document.body.firstChild.firstChild, 0);
|
||||
description = "Initialized with range collapsed at start of text node:";
|
||||
iter.initWithRange(Ci.nsIScriptableContentIterator.POST_ORDER_ITERATOR, range);
|
||||
is(SpecialPowers.unwrap(iter.currentNode), null,
|
||||
`${description} currentNode should be null immediately after initialization (got: ${getNodeDescription(iter.currentNode)})`);
|
||||
ok(iter.isDone, `${description} isDone should be true immediately after initialization`);
|
||||
|
||||
iter.first();
|
||||
is(SpecialPowers.unwrap(iter.currentNode), null,
|
||||
`${description} currentNode should be null even after calling first() (got: ${getNodeDescription(iter.currentNode)})`);
|
||||
ok(iter.isDone, `${description} isDone should be true even after calling first()`);
|
||||
|
||||
/**
|
||||
* Tests to initializing with collapsed range at start of a text node.
|
||||
* XXX In this case, content iterator lists up the text node. Not sure if this is intentional difference
|
||||
* from initWithRange().
|
||||
*/
|
||||
description = "Initialized with a position at start of text node:";
|
||||
iter.initWithPositions(Ci.nsIScriptableContentIterator.POST_ORDER_ITERATOR,
|
||||
document.body.firstChild.firstChild, 0, document.body.firstChild.firstChild, 0);
|
||||
is(SpecialPowers.unwrap(iter.currentNode), document.body.firstChild.firstChild,
|
||||
`${description} currentNode should be the text node immediately after initialization (got: ${getNodeDescription(iter.currentNode)})`);
|
||||
ok(!iter.isDone, `${description} isDone shouldn't be true immediately after initialization`);
|
||||
|
||||
iter.next();
|
||||
is(SpecialPowers.unwrap(iter.currentNode), null,
|
||||
`${description} currentNode should be null after calling next() (got: ${getNodeDescription(iter.currentNode)})`);
|
||||
ok(iter.isDone, `${description} isDone should be true after calling next()`);
|
||||
|
||||
/**
|
||||
* Tests to initializing with collapsed range at end of a text node.
|
||||
*/
|
||||
range = document.createRange();
|
||||
range.collapse(document.body.firstChild.firstChild, document.body.firstChild.firstChild.length);
|
||||
description = "Initialized with range collapsed at end of text node:";
|
||||
iter.initWithRange(Ci.nsIScriptableContentIterator.POST_ORDER_ITERATOR, range);
|
||||
is(SpecialPowers.unwrap(iter.currentNode), null,
|
||||
`${description} currentNode should be null immediately after initialization (got: ${getNodeDescription(iter.currentNode)})`);
|
||||
ok(iter.isDone, `${description} isDone should be true immediately after initialization`);
|
||||
|
||||
iter.first();
|
||||
is(SpecialPowers.unwrap(iter.currentNode), null,
|
||||
`${description} currentNode should be null even after calling first() (got: ${getNodeDescription(iter.currentNode)})`);
|
||||
ok(iter.isDone, `${description} isDone should be true even after calling first()`);
|
||||
|
||||
/**
|
||||
* Tests to initializing with collapsed range at end of a text node.
|
||||
* XXX In this case, content iterator lists up the text node. Not sure if this is intentional difference
|
||||
* from initWithRange().
|
||||
*/
|
||||
description = "Initialized with a position at end of text node:";
|
||||
iter.initWithPositions(Ci.nsIScriptableContentIterator.POST_ORDER_ITERATOR,
|
||||
document.body.firstChild.firstChild, document.body.firstChild.firstChild.length,
|
||||
document.body.firstChild.firstChild, document.body.firstChild.firstChild.length);
|
||||
is(SpecialPowers.unwrap(iter.currentNode), document.body.firstChild.firstChild,
|
||||
`${description} currentNode should be the text node immediately after initialization (got: ${getNodeDescription(iter.currentNode)})`);
|
||||
ok(!iter.isDone, `${description} isDone shouldn't be true immediately after initialization`);
|
||||
|
||||
iter.next();
|
||||
is(SpecialPowers.unwrap(iter.currentNode), null,
|
||||
`${description} currentNode should be null after calling next() (got: ${getNodeDescription(iter.currentNode)})`);
|
||||
ok(iter.isDone, `${description} isDone should be true after calling next()`);
|
||||
|
||||
/**
|
||||
* Tests to initializing with collapsed range at middle of a text node.
|
||||
*/
|
||||
range = document.createRange();
|
||||
range.collapse(document.body.firstChild.firstChild, document.body.firstChild.firstChild.length / 2);
|
||||
description = "Initialized with range collapsed at end of text node:";
|
||||
iter.initWithRange(Ci.nsIScriptableContentIterator.POST_ORDER_ITERATOR, range);
|
||||
is(SpecialPowers.unwrap(iter.currentNode), null,
|
||||
`${description} currentNode should be null immediately after initialization (got: ${getNodeDescription(iter.currentNode)})`);
|
||||
ok(iter.isDone, `${description} isDone should be true immediately after initialization`);
|
||||
|
||||
iter.first();
|
||||
is(SpecialPowers.unwrap(iter.currentNode), null,
|
||||
`${description} currentNode should be null even after calling first() (got: ${getNodeDescription(iter.currentNode)})`);
|
||||
ok(iter.isDone, `${description} isDone should be true even after calling first()`);
|
||||
|
||||
/**
|
||||
* Tests to initializing with collapsed range at middle of a text node.
|
||||
* XXX In this case, content iterator lists up the text node. Not sure if this is intentional difference
|
||||
* from initWithRange().
|
||||
*/
|
||||
description = "Initialized with a position at end of text node:";
|
||||
iter.initWithPositions(Ci.nsIScriptableContentIterator.POST_ORDER_ITERATOR,
|
||||
document.body.firstChild.firstChild, document.body.firstChild.firstChild.length / 2,
|
||||
document.body.firstChild.firstChild, document.body.firstChild.firstChild.length / 2);
|
||||
is(SpecialPowers.unwrap(iter.currentNode), document.body.firstChild.firstChild,
|
||||
`${description} currentNode should be the text node immediately after initialization (got: ${getNodeDescription(iter.currentNode)})`);
|
||||
ok(!iter.isDone, `${description} isDone shouldn't be true immediately after initialization`);
|
||||
|
||||
iter.next();
|
||||
is(SpecialPowers.unwrap(iter.currentNode), null,
|
||||
`${description} currentNode should be null after calling next() (got: ${getNodeDescription(iter.currentNode)})`);
|
||||
ok(iter.isDone, `${description} isDone should be true after calling next()`);
|
||||
|
||||
/**
|
||||
* Tests to initializing with a range selecting all text in a text node.
|
||||
*/
|
||||
range = document.createRange();
|
||||
range.setStart(document.body.firstChild.firstChild, 0);
|
||||
range.setEnd(document.body.firstChild.firstChild, document.body.firstChild.firstChild.length);
|
||||
description = "Initialized with range selecting all text in text node:";
|
||||
iter.initWithRange(Ci.nsIScriptableContentIterator.POST_ORDER_ITERATOR, range);
|
||||
is(SpecialPowers.unwrap(iter.currentNode), document.body.firstChild.firstChild,
|
||||
`${description} currentNode should be the text node immediately after initialization (got: ${getNodeDescription(iter.currentNode)})`);
|
||||
ok(!iter.isDone, `${description} isDone shouldn't be true immediately after initialization`);
|
||||
|
||||
iter.next();
|
||||
is(SpecialPowers.unwrap(iter.currentNode), null,
|
||||
`${description} currentNode should be null after calling next() (got: ${getNodeDescription(iter.currentNode)})`);
|
||||
ok(iter.isDone, `${description} isDone should be true after calling next()`);
|
||||
|
||||
/**
|
||||
* Tests to initializing with positions selecting all text in a text node.
|
||||
*/
|
||||
description = "Initialized with positions selecting all text in text node:";
|
||||
iter.initWithPositions(Ci.nsIScriptableContentIterator.POST_ORDER_ITERATOR,
|
||||
document.body.firstChild.firstChild, 0,
|
||||
document.body.firstChild.firstChild, document.body.firstChild.firstChild.length);
|
||||
is(SpecialPowers.unwrap(iter.currentNode), document.body.firstChild.firstChild,
|
||||
`${description} currentNode should be the text node immediately after initialization (got: ${getNodeDescription(iter.currentNode)})`);
|
||||
ok(!iter.isDone, `${description} isDone shouldn't be true immediately after initialization`);
|
||||
|
||||
iter.next();
|
||||
is(SpecialPowers.unwrap(iter.currentNode), null,
|
||||
`${description} currentNode should be null after calling next() (got: ${getNodeDescription(iter.currentNode)})`);
|
||||
ok(iter.isDone, `${description} isDone should be true after calling next()`);
|
||||
|
||||
/**
|
||||
* Basic tests with complicated tree.
|
||||
*/
|
||||
function check(aIter, aExpectedResult, aDescription) {
|
||||
if (aExpectedResult.length > 0) {
|
||||
is(SpecialPowers.unwrap(aIter.currentNode), aExpectedResult[0],
|
||||
`${aDescription}: currentNode should be the text node immediately after initialization (got: ${getNodeDescription(aIter.currentNode)}, expected: ${getNodeDescription(aExpectedResult[0])})`);
|
||||
ok(!aIter.isDone, `${aDescription}: isDone shouldn't be true immediately after initialization`);
|
||||
|
||||
aIter.first();
|
||||
is(SpecialPowers.unwrap(aIter.currentNode), aExpectedResult[0],
|
||||
`${aDescription}: currentNode should be the text node after calling first() (got: ${getNodeDescription(aIter.currentNode)}, expected: ${getNodeDescription(aExpectedResult[0])})`);
|
||||
ok(!aIter.isDone, `${aDescription}: isDone shouldn't be true after calling first()`);
|
||||
|
||||
for (let expected of aExpectedResult) {
|
||||
is(SpecialPowers.unwrap(aIter.currentNode), expected,
|
||||
`${aDescription}: currentNode should be the node (got: ${getNodeDescription(aIter.currentNode)}, expected: ${getNodeDescription(expected)})`);
|
||||
ok(!aIter.isDone, `${aDescription}: isDone shouldn't be true when ${getNodeDescription(expected)} is expected`);
|
||||
aIter.next();
|
||||
}
|
||||
|
||||
is(SpecialPowers.unwrap(aIter.currentNode), null,
|
||||
`${aDescription}: currentNode should be null after calling next() finally (got: ${getNodeDescription(aIter.currentNode)}`);
|
||||
ok(aIter.isDone, `${aDescription}: isDone should be true after calling next() finally`);
|
||||
} else {
|
||||
is(SpecialPowers.unwrap(aIter.currentNode), null,
|
||||
`${aDescription}: currentNode should be null immediately after initialization (got: ${getNodeDescription(aIter.currentNode)})`);
|
||||
ok(aIter.isDone, `${aDescription}: isDone should be true immediately after initialization`);
|
||||
|
||||
aIter.first();
|
||||
is(SpecialPowers.unwrap(aIter.currentNode), null,
|
||||
`${aDescription}: currentNode should be null after calling first() (got: ${getNodeDescription(aIter.currentNode)})`);
|
||||
ok(aIter.isDone, `${aDescription}: isDone should be true after calling first()`);
|
||||
}
|
||||
}
|
||||
|
||||
document.body.innerHTML = "<p>" +
|
||||
"Here is <b>bold</b> and <i><u>underlined and </u>italic </i><span>or no style text.</span><br>" +
|
||||
"</p>" +
|
||||
"<p>" +
|
||||
"Here is an <input> element: <input type=\"text\" value=\"default value\"><br>\n" +
|
||||
"and a <textarea> element: <textarea>text area's text node</textarea><br><br>\n" +
|
||||
"<!-- and here is comment node -->" +
|
||||
"</p>";
|
||||
|
||||
let expectedResult =
|
||||
[document.body.firstChild.firstChild, // the first text node
|
||||
document.body.firstChild.firstChild.nextSibling.firstChild, // text in <b>
|
||||
document.body.firstChild.firstChild.nextSibling, // <b>
|
||||
document.body.firstChild.firstChild.nextSibling.nextSibling, // text next to <b>
|
||||
document.body.firstChild.firstChild.nextSibling.nextSibling.nextSibling.firstChild.firstChild, // text in <u>
|
||||
document.body.firstChild.firstChild.nextSibling.nextSibling.nextSibling.firstChild, // <u>
|
||||
document.body.firstChild.firstChild.nextSibling.nextSibling.nextSibling.firstChild.nextSibling, // text next to <u>
|
||||
document.body.firstChild.firstChild.nextSibling.nextSibling.nextSibling, // <i>
|
||||
document.body.firstChild.firstChild.nextSibling.nextSibling.nextSibling.nextSibling.firstChild, // text in <span>
|
||||
document.body.firstChild.firstChild.nextSibling.nextSibling.nextSibling.nextSibling, // <span>
|
||||
document.body.firstChild.firstChild.nextSibling.nextSibling.nextSibling.nextSibling.nextSibling, // <br> next to <span>
|
||||
document.body.firstChild, // first <p>
|
||||
document.body.firstChild.nextSibling.firstChild, // the first text node in second <p>
|
||||
document.body.firstChild.nextSibling.firstChild.nextSibling, // <input>
|
||||
document.body.firstChild.nextSibling.firstChild.nextSibling.nextSibling, // <br> next to <input>
|
||||
document.body.firstChild.nextSibling.firstChild.nextSibling.nextSibling.nextSibling, // text next to <input>
|
||||
document.body.firstChild.nextSibling.firstChild.nextSibling.nextSibling.nextSibling.nextSibling.firstChild, // text in <textarea>
|
||||
document.body.firstChild.nextSibling.firstChild.nextSibling.nextSibling.nextSibling.nextSibling, // <textarea>
|
||||
document.body.firstChild.nextSibling.firstChild.nextSibling.nextSibling.nextSibling.nextSibling.nextSibling, // <br> next to <textarea>
|
||||
document.body.firstChild.nextSibling.firstChild.nextSibling.nextSibling.nextSibling.nextSibling.nextSibling.nextSibling, // <br> next to <br>
|
||||
document.body.firstChild.nextSibling.firstChild.nextSibling.nextSibling.nextSibling.nextSibling.nextSibling.nextSibling.nextSibling, // text next to <br>
|
||||
document.body.firstChild.nextSibling.firstChild.nextSibling.nextSibling.nextSibling.nextSibling.nextSibling.nextSibling.nextSibling.nextSibling, // comment
|
||||
document.body.firstChild.nextSibling, // second <p>
|
||||
document.body]; // <body>
|
||||
|
||||
iter.initWithRootNode(Ci.nsIScriptableContentIterator.POST_ORDER_ITERATOR, document.body);
|
||||
check(iter, expectedResult, "Initialized with the <body> as root element:");
|
||||
|
||||
/**
|
||||
* Selects the <body> with a range.
|
||||
*/
|
||||
range = document.createRange();
|
||||
range.selectNode(document.body);
|
||||
iter.initWithRange(Ci.nsIScriptableContentIterator.POST_ORDER_ITERATOR, range);
|
||||
check(iter, expectedResult, "Initialized with range selecting the <body>");
|
||||
|
||||
/**
|
||||
* Selects the <body> with positions.
|
||||
*/
|
||||
iter.initWithPositions(Ci.nsIScriptableContentIterator.POST_ORDER_ITERATOR,
|
||||
range.startContainer, range.startOffset, range.endContainer, range.endOffset);
|
||||
check(iter, expectedResult, "Initialized with positions selecting the <body>");
|
||||
|
||||
/**
|
||||
* Selects all children in the <body> with a range.
|
||||
*/
|
||||
expectedResult.pop(); // <body> shouldn't be listed up.
|
||||
range = document.createRange();
|
||||
range.selectNodeContents(document.body);
|
||||
iter.initWithRange(Ci.nsIScriptableContentIterator.POST_ORDER_ITERATOR, range);
|
||||
check(iter, expectedResult, "Initialized with range selecting all children in the <body>");
|
||||
|
||||
/**
|
||||
* Selects all children in the <body> with positions.
|
||||
*/
|
||||
iter.initWithPositions(Ci.nsIScriptableContentIterator.POST_ORDER_ITERATOR,
|
||||
range.startContainer, range.startOffset, range.endContainer, range.endOffset);
|
||||
check(iter, expectedResult, "Initialized with positions selecting all children in the <body>");
|
||||
|
||||
/**
|
||||
* range/positions around elements.
|
||||
*/
|
||||
document.body.innerHTML = "abc<b>def</b><i>ghi</i>jkl";
|
||||
range = document.createRange();
|
||||
|
||||
range.setStart(document.body.firstChild, 0);
|
||||
range.setEnd(document.body.firstChild.nextSibling.firstChild, 2);
|
||||
iter.initWithRange(Ci.nsIScriptableContentIterator.POST_ORDER_ITERATOR, range);
|
||||
check(iter,
|
||||
[document.body.firstChild, // text before <b>
|
||||
document.body.firstChild.nextSibling.firstChild], // text in <b>
|
||||
"Initialized with range selecting '[abc<b>de]f'");
|
||||
iter.initWithPositions(Ci.nsIScriptableContentIterator.POST_ORDER_ITERATOR,
|
||||
range.startContainer, range.startOffset,
|
||||
range.endContainer, range.endOffset);
|
||||
check(iter,
|
||||
[document.body.firstChild, // text before <b>
|
||||
document.body.firstChild.nextSibling.firstChild], // text in <b>
|
||||
"Initialized with positions selecting '[abc<b>de]f'");
|
||||
|
||||
range.setStart(document.body.firstChild, 2);
|
||||
range.setEnd(document.body.firstChild.nextSibling.firstChild, 2);
|
||||
iter.initWithRange(Ci.nsIScriptableContentIterator.POST_ORDER_ITERATOR, range);
|
||||
check(iter,
|
||||
[document.body.firstChild, // text before <b>
|
||||
document.body.firstChild.nextSibling.firstChild], // text in <b>
|
||||
"Initialized with range selecting 'ab[c<b>de]f'");
|
||||
iter.initWithPositions(Ci.nsIScriptableContentIterator.POST_ORDER_ITERATOR,
|
||||
range.startContainer, range.startOffset,
|
||||
range.endContainer, range.endOffset);
|
||||
check(iter,
|
||||
[document.body.firstChild, // text before <b>
|
||||
document.body.firstChild.nextSibling.firstChild], // text in <b>
|
||||
"Initialized with positions selecting 'ab[c<b>de]f'");
|
||||
|
||||
range.setStart(document.body.firstChild, 3);
|
||||
range.setEnd(document.body.firstChild.nextSibling.firstChild, 2);
|
||||
iter.initWithRange(Ci.nsIScriptableContentIterator.POST_ORDER_ITERATOR, range);
|
||||
check(iter,
|
||||
[document.body.firstChild, // text before <b>
|
||||
document.body.firstChild.nextSibling.firstChild], // text in <b>
|
||||
"Initialized with range selecting 'abc[<b>de]f'");
|
||||
iter.initWithPositions(Ci.nsIScriptableContentIterator.POST_ORDER_ITERATOR,
|
||||
range.startContainer, range.startOffset,
|
||||
range.endContainer, range.endOffset);
|
||||
check(iter,
|
||||
[document.body.firstChild, // text before <b>
|
||||
document.body.firstChild.nextSibling.firstChild], // text in <b>
|
||||
"Initialized with positions selecting 'abc[<b>de]f'");
|
||||
|
||||
range.setStart(document.body, 1);
|
||||
range.setEnd(document.body.firstChild.nextSibling.firstChild, 2);
|
||||
iter.initWithRange(Ci.nsIScriptableContentIterator.POST_ORDER_ITERATOR, range);
|
||||
check(iter,
|
||||
[document.body.firstChild.nextSibling.firstChild], // text in <b>
|
||||
"Initialized with range selecting 'abc{<b>de]f'");
|
||||
iter.initWithPositions(Ci.nsIScriptableContentIterator.POST_ORDER_ITERATOR,
|
||||
range.startContainer, range.startOffset,
|
||||
range.endContainer, range.endOffset);
|
||||
check(iter,
|
||||
[document.body.firstChild.nextSibling.firstChild], // text in <b>
|
||||
"Initialized with positions selecting 'abc{<b>de]f'");
|
||||
|
||||
range.setStart(document.body.firstChild.nextSibling, 0);
|
||||
range.setEnd(document.body.firstChild.nextSibling.firstChild, 2);
|
||||
iter.initWithRange(Ci.nsIScriptableContentIterator.POST_ORDER_ITERATOR, range);
|
||||
check(iter,
|
||||
[document.body.firstChild.nextSibling.firstChild], // text in <b>
|
||||
"Initialized with range selecting '<b>{de]f'");
|
||||
iter.initWithPositions(Ci.nsIScriptableContentIterator.POST_ORDER_ITERATOR,
|
||||
range.startContainer, range.startOffset,
|
||||
range.endContainer, range.endOffset);
|
||||
check(iter,
|
||||
[document.body.firstChild.nextSibling.firstChild], // text in <b>
|
||||
"Initialized with positions selecting '<b>{de]f'");
|
||||
|
||||
range.setStart(document.body.firstChild.nextSibling, 0);
|
||||
range.setEnd(document.body.firstChild.nextSibling.firstChild, 3);
|
||||
iter.initWithRange(Ci.nsIScriptableContentIterator.POST_ORDER_ITERATOR, range);
|
||||
check(iter,
|
||||
[document.body.firstChild.nextSibling.firstChild], // text in <b>
|
||||
"Initialized with range selecting '<b>{def]</b>'");
|
||||
iter.initWithPositions(Ci.nsIScriptableContentIterator.POST_ORDER_ITERATOR,
|
||||
range.startContainer, range.startOffset,
|
||||
range.endContainer, range.endOffset);
|
||||
check(iter,
|
||||
[document.body.firstChild.nextSibling.firstChild], // text in <b>
|
||||
"Initialized with positions selecting '<b>{def]</b>'");
|
||||
|
||||
range.setStart(document.body.firstChild.nextSibling, 0);
|
||||
range.setEnd(document.body.firstChild.nextSibling, 1);
|
||||
iter.initWithRange(Ci.nsIScriptableContentIterator.POST_ORDER_ITERATOR, range);
|
||||
check(iter,
|
||||
[document.body.firstChild.nextSibling.firstChild], // text in <b>
|
||||
"Initialized with range selecting '<b>{def}</b>'");
|
||||
iter.initWithPositions(Ci.nsIScriptableContentIterator.POST_ORDER_ITERATOR,
|
||||
range.startContainer, range.startOffset,
|
||||
range.endContainer, range.endOffset);
|
||||
check(iter,
|
||||
[document.body.firstChild.nextSibling.firstChild], // text in <b>
|
||||
"Initialized with positions selecting '<b>{def}</b>'");
|
||||
|
||||
range.setStart(document.body.firstChild.nextSibling, 0);
|
||||
range.setEnd(document.body, 2);
|
||||
iter.initWithRange(Ci.nsIScriptableContentIterator.POST_ORDER_ITERATOR, range);
|
||||
check(iter,
|
||||
[document.body.firstChild.nextSibling.firstChild, // text in <b>
|
||||
document.body.firstChild.nextSibling], // <b>
|
||||
"Initialized with range selecting '<b>{def</b>}<i>ghi'");
|
||||
iter.initWithPositions(Ci.nsIScriptableContentIterator.POST_ORDER_ITERATOR,
|
||||
range.startContainer, range.startOffset,
|
||||
range.endContainer, range.endOffset);
|
||||
check(iter,
|
||||
[document.body.firstChild.nextSibling.firstChild, // text in <b>
|
||||
document.body.firstChild.nextSibling], // <b>
|
||||
"Initialized with positions selecting '<b>{def</b>}<i>ghi'");
|
||||
|
||||
range.setStart(document.body.firstChild.nextSibling.firstChild, 3);
|
||||
range.setEnd(document.body, 2);
|
||||
iter.initWithRange(Ci.nsIScriptableContentIterator.POST_ORDER_ITERATOR, range);
|
||||
check(iter,
|
||||
[document.body.firstChild.nextSibling.firstChild, // text in <b>
|
||||
document.body.firstChild.nextSibling], // <b>
|
||||
"Initialized with range selecting '<b>def[</b>}<i>ghi'");
|
||||
iter.initWithPositions(Ci.nsIScriptableContentIterator.POST_ORDER_ITERATOR,
|
||||
range.startContainer, range.startOffset,
|
||||
range.endContainer, range.endOffset);
|
||||
check(iter,
|
||||
[document.body.firstChild.nextSibling.firstChild, // text in <b>
|
||||
document.body.firstChild.nextSibling], // <b>
|
||||
"Initialized with positions selecting '<b>def[</b>}<i>ghi'");
|
||||
|
||||
range.setStart(document.body.firstChild.nextSibling, 1);
|
||||
range.setEnd(document.body, 2);
|
||||
iter.initWithRange(Ci.nsIScriptableContentIterator.POST_ORDER_ITERATOR, range);
|
||||
check(iter,
|
||||
[document.body.firstChild.nextSibling], // <b>
|
||||
"Initialized with range selecting '<b>def{</b>}<i>ghi'");
|
||||
iter.initWithPositions(Ci.nsIScriptableContentIterator.POST_ORDER_ITERATOR,
|
||||
range.startContainer, range.startOffset,
|
||||
range.endContainer, range.endOffset);
|
||||
check(iter,
|
||||
[document.body.firstChild.nextSibling], // <b>
|
||||
"Initialized with positions selecting '<b>def{</b>}<i>ghi'");
|
||||
|
||||
range.setStart(document.body.firstChild.nextSibling, 1);
|
||||
range.setEnd(document.body.firstChild.nextSibling.nextSibling, 0);
|
||||
iter.initWithRange(Ci.nsIScriptableContentIterator.POST_ORDER_ITERATOR, range);
|
||||
check(iter,
|
||||
[document.body.firstChild.nextSibling], // <b>
|
||||
"Initialized with range selecting '<b>def{</b><i>}ghi'");
|
||||
iter.initWithPositions(Ci.nsIScriptableContentIterator.POST_ORDER_ITERATOR,
|
||||
range.startContainer, range.startOffset,
|
||||
range.endContainer, range.endOffset);
|
||||
check(iter,
|
||||
[document.body.firstChild.nextSibling], // <b>
|
||||
"Initialized with positions selecting '<b>def{</b><i>}ghi'");
|
||||
|
||||
range.setStart(document.body.firstChild.nextSibling, 1);
|
||||
range.setEnd(document.body.firstChild.nextSibling.nextSibling.firstChild, 0);
|
||||
iter.initWithRange(Ci.nsIScriptableContentIterator.POST_ORDER_ITERATOR, range);
|
||||
check(iter,
|
||||
[document.body.firstChild.nextSibling, // <b>
|
||||
document.body.firstChild.nextSibling.nextSibling.firstChild], // text in <i>
|
||||
"Initialized with range selecting '<b>def{</b><i>]ghi'");
|
||||
iter.initWithPositions(Ci.nsIScriptableContentIterator.POST_ORDER_ITERATOR,
|
||||
range.startContainer, range.startOffset,
|
||||
range.endContainer, range.endOffset);
|
||||
check(iter,
|
||||
[document.body.firstChild.nextSibling, // <b>
|
||||
document.body.firstChild.nextSibling.nextSibling.firstChild], // text in <i>
|
||||
"Initialized with positions selecting '<b>def{</b><i>]ghi'");
|
||||
|
||||
range.setStart(document.body.firstChild.nextSibling.nextSibling, 0);
|
||||
range.setEnd(document.body, 3);
|
||||
iter.initWithRange(Ci.nsIScriptableContentIterator.POST_ORDER_ITERATOR, range);
|
||||
check(iter,
|
||||
[document.body.firstChild.nextSibling.nextSibling.firstChild, // text in <i>
|
||||
document.body.firstChild.nextSibling.nextSibling], // <i>
|
||||
"Initialized with range selecting '<i>{ghi</i>}jkl'");
|
||||
iter.initWithPositions(Ci.nsIScriptableContentIterator.POST_ORDER_ITERATOR,
|
||||
range.startContainer, range.startOffset,
|
||||
range.endContainer, range.endOffset);
|
||||
check(iter,
|
||||
[document.body.firstChild.nextSibling.nextSibling.firstChild, // text in <i>
|
||||
document.body.firstChild.nextSibling.nextSibling], // <i>
|
||||
"Initialized with positions selecting '<i>{ghi</i>}jkl'");
|
||||
|
||||
range.setStart(document.body.firstChild.nextSibling.nextSibling.firstChild, 3);
|
||||
range.setEnd(document.body, 3);
|
||||
iter.initWithRange(Ci.nsIScriptableContentIterator.POST_ORDER_ITERATOR, range);
|
||||
check(iter,
|
||||
[document.body.firstChild.nextSibling.nextSibling.firstChild, // text in <i>
|
||||
document.body.firstChild.nextSibling.nextSibling], // <i>
|
||||
"Initialized with range selecting '<i>ghi[</i>}jkl'");
|
||||
iter.initWithPositions(Ci.nsIScriptableContentIterator.POST_ORDER_ITERATOR,
|
||||
range.startContainer, range.startOffset,
|
||||
range.endContainer, range.endOffset);
|
||||
check(iter,
|
||||
[document.body.firstChild.nextSibling.nextSibling.firstChild, // text in <i>
|
||||
document.body.firstChild.nextSibling.nextSibling], // <i>
|
||||
"Initialized with positions selecting '<i>ghi[</i>}jkl'");
|
||||
|
||||
range.setStart(document.body.firstChild.nextSibling.nextSibling, 1);
|
||||
range.setEnd(document.body, 3);
|
||||
iter.initWithRange(Ci.nsIScriptableContentIterator.POST_ORDER_ITERATOR, range);
|
||||
check(iter,
|
||||
[document.body.firstChild.nextSibling.nextSibling], // <i>
|
||||
"Initialized with range selecting '<i>ghi{</i>}jkl'");
|
||||
iter.initWithPositions(Ci.nsIScriptableContentIterator.POST_ORDER_ITERATOR,
|
||||
range.startContainer, range.startOffset,
|
||||
range.endContainer, range.endOffset);
|
||||
check(iter,
|
||||
[document.body.firstChild.nextSibling.nextSibling], // <i>
|
||||
"Initialized with positions selecting '<i>ghi{</i>}jkl'");
|
||||
|
||||
range.setStart(document.body.firstChild.nextSibling.nextSibling, 1);
|
||||
range.setEnd(document.body.firstChild.nextSibling.nextSibling.nextSibling, 0);
|
||||
iter.initWithRange(Ci.nsIScriptableContentIterator.POST_ORDER_ITERATOR, range);
|
||||
check(iter,
|
||||
[document.body.firstChild.nextSibling.nextSibling, // <i>
|
||||
document.body.firstChild.nextSibling.nextSibling.nextSibling], // text after <i>
|
||||
"Initialized with range selecting '<i>ghi{</i>]jkl'");
|
||||
iter.initWithPositions(Ci.nsIScriptableContentIterator.POST_ORDER_ITERATOR,
|
||||
range.startContainer, range.startOffset,
|
||||
range.endContainer, range.endOffset);
|
||||
check(iter,
|
||||
[document.body.firstChild.nextSibling.nextSibling, // <i>
|
||||
document.body.firstChild.nextSibling.nextSibling.nextSibling], // text after <i>
|
||||
"Initialized with positions selecting '<i>ghi{</i>]jkl'");
|
||||
|
||||
/**
|
||||
* range/positions around <br> elements.
|
||||
*/
|
||||
document.body.innerHTML = "abc<br>def";
|
||||
range = document.createRange();
|
||||
range.setStart(document.body.firstChild, 3);
|
||||
range.setEnd(document.body.firstChild.nextSibling.nextSibling, 0);
|
||||
iter.initWithRange(Ci.nsIScriptableContentIterator.POST_ORDER_ITERATOR, range);
|
||||
check(iter,
|
||||
[document.body.firstChild, // text before <br>
|
||||
document.body.firstChild.nextSibling, // <br>
|
||||
document.body.firstChild.nextSibling.nextSibling], // text after <br>
|
||||
"Initialized with range selecting 'abc[<br>]def'");
|
||||
iter.initWithPositions(Ci.nsIScriptableContentIterator.POST_ORDER_ITERATOR,
|
||||
range.startContainer, range.startOffset,
|
||||
range.endContainer, range.endOffset);
|
||||
check(iter,
|
||||
[document.body.firstChild, // text before <br>
|
||||
document.body.firstChild.nextSibling, // <br>
|
||||
document.body.firstChild.nextSibling.nextSibling], // text after <br>
|
||||
"Initialized with positions selecting 'abc[<br>]def'");
|
||||
|
||||
range.setStart(document.body, 1);
|
||||
range.setEnd(document.body.firstChild.nextSibling.nextSibling, 0);
|
||||
iter.initWithRange(Ci.nsIScriptableContentIterator.POST_ORDER_ITERATOR, range);
|
||||
check(iter,
|
||||
[document.body.firstChild.nextSibling, // <br>
|
||||
document.body.firstChild.nextSibling.nextSibling], // text after <br>
|
||||
"Initialized with range selecting 'abc{<br>]def'");
|
||||
iter.initWithPositions(Ci.nsIScriptableContentIterator.POST_ORDER_ITERATOR,
|
||||
range.startContainer, range.startOffset,
|
||||
range.endContainer, range.endOffset);
|
||||
check(iter,
|
||||
[document.body.firstChild.nextSibling, // <br>
|
||||
document.body.firstChild.nextSibling.nextSibling], // text after <br>
|
||||
"Initialized with positions selecting 'abc{<br>]def'");
|
||||
|
||||
range.setStart(document.body.firstChild.nextSibling, 0);
|
||||
range.setEnd(document.body.firstChild.nextSibling.nextSibling, 0);
|
||||
iter.initWithRange(Ci.nsIScriptableContentIterator.POST_ORDER_ITERATOR, range);
|
||||
check(iter,
|
||||
[document.body.firstChild.nextSibling, // <br>
|
||||
document.body.firstChild.nextSibling.nextSibling], // text after <br>
|
||||
"Initialized with range selecting 'abc{<br>]def' (starting in <br>)");
|
||||
iter.initWithPositions(Ci.nsIScriptableContentIterator.POST_ORDER_ITERATOR,
|
||||
range.startContainer, range.startOffset,
|
||||
range.endContainer, range.endOffset);
|
||||
check(iter,
|
||||
[document.body.firstChild.nextSibling, // <br>
|
||||
document.body.firstChild.nextSibling.nextSibling], // text after <br>
|
||||
"Initialized with positions selecting 'abc{<br>]def' (starting in <br>)");
|
||||
|
||||
range.setStart(document.body, 1);
|
||||
range.setEnd(document.body, 2);
|
||||
iter.initWithRange(Ci.nsIScriptableContentIterator.POST_ORDER_ITERATOR, range);
|
||||
check(iter,
|
||||
[document.body.firstChild.nextSibling], // <br>
|
||||
"Initialized with range selecting 'abc{<br>}def'");
|
||||
iter.initWithPositions(Ci.nsIScriptableContentIterator.POST_ORDER_ITERATOR,
|
||||
range.startContainer, range.startOffset,
|
||||
range.endContainer, range.endOffset);
|
||||
check(iter,
|
||||
[document.body.firstChild.nextSibling], // <br>
|
||||
"Initialized with positions selecting 'abc{<br>}def'");
|
||||
|
||||
range.setStart(document.body.firstChild, 3);
|
||||
range.setEnd(document.body.firstChild.nextSibling, 0);
|
||||
iter.initWithRange(Ci.nsIScriptableContentIterator.POST_ORDER_ITERATOR, range);
|
||||
check(iter,
|
||||
[document.body.firstChild], // text before <br>
|
||||
"Initialized with range selecting 'abc[}<br>def' (ending in <br>)");
|
||||
iter.initWithPositions(Ci.nsIScriptableContentIterator.POST_ORDER_ITERATOR,
|
||||
range.startContainer, range.startOffset,
|
||||
range.endContainer, range.endOffset);
|
||||
check(iter,
|
||||
[document.body.firstChild], // text before <br>
|
||||
"Initialized with positions selecting 'abc[}<br>def' (ending in <br>)");
|
||||
|
||||
finish();
|
||||
});
|
||||
</script>
|
||||
</head>
|
||||
<body></body>
|
||||
</html>
|
|
@ -0,0 +1,869 @@
|
|||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>Test for pre-order content iterator</title>
|
||||
<script src="/tests/SimpleTest/SimpleTest.js"></script>
|
||||
<link rel="stylesheet" href="/tests/SimpleTest/test.css">
|
||||
<script>
|
||||
var Cc = SpecialPowers.Cc;
|
||||
var Ci = SpecialPowers.Ci;
|
||||
function finish() {
|
||||
// The SimpleTest may require usual elements in the template, but they shouldn't be during test.
|
||||
// So, let's create them at end of the test.
|
||||
document.body.innerHTML = '<div id="display"></div><div id="content"></div><pre id="test"></pre>';
|
||||
SimpleTest.finish();
|
||||
}
|
||||
|
||||
function createContentIterator() {
|
||||
return Cc["@mozilla.org/scriptable-content-iterator;1"]
|
||||
.createInstance(Ci.nsIScriptableContentIterator);
|
||||
}
|
||||
|
||||
function getNodeDescription(aNode) {
|
||||
if (aNode === undefined) {
|
||||
return "undefine";
|
||||
}
|
||||
if (aNode === null) {
|
||||
return "null";
|
||||
}
|
||||
function getElementDescription(aElement) {
|
||||
if (aElement.tagName === "BR") {
|
||||
if (aElement.previousSibling) {
|
||||
return `<br> element after ${getNodeDescription(aElement.previousSibling)}`;
|
||||
}
|
||||
return `<br> element in ${getElementDescription(aElement.parentElement)}`;
|
||||
}
|
||||
let hasHint = aElement == document.body;
|
||||
let tag = `<${aElement.tagName.toLowerCase()}`;
|
||||
if (aElement.getAttribute("id")) {
|
||||
tag += ` id="${aElement.getAttribute("id")}"`;
|
||||
hasHint = true;
|
||||
}
|
||||
if (aElement.getAttribute("class")) {
|
||||
tag += ` class="${aElement.getAttribute("class")}"`;
|
||||
hasHint = true;
|
||||
}
|
||||
if (aElement.getAttribute("type")) {
|
||||
tag += ` type="${aElement.getAttribute("type")}"`;
|
||||
}
|
||||
if (aElement.getAttribute("name")) {
|
||||
tag += ` name="${aElement.getAttribute("name")}"`;
|
||||
}
|
||||
if (aElement.getAttribute("value")) {
|
||||
tag += ` value="${aElement.getAttribute("value")}"`;
|
||||
hasHint = true;
|
||||
}
|
||||
if (aElement.getAttribute("style")) {
|
||||
tag += ` style="${aElement.getAttribute("style")}"`;
|
||||
hasHint = true;
|
||||
}
|
||||
if (hasHint) {
|
||||
return tag + ">";
|
||||
}
|
||||
return `${tag}> in ${getElementDescription(aElement.parentElement)}`;
|
||||
}
|
||||
switch (aNode.nodeType) {
|
||||
case aNode.TEXT_NODE:
|
||||
return `text node, "${aNode.wholeText.replace(/\n/g, '\\n')}"`;
|
||||
case aNode.COMMENT_NODE:
|
||||
return `comment node, "${aNode.data.replace(/\n/g, '\\n')}"`;
|
||||
case aNode.ELEMENT_NODE:
|
||||
return getElementDescription(SpecialPowers.unwrap(aNode));
|
||||
default:
|
||||
return "unknown node";
|
||||
}
|
||||
}
|
||||
|
||||
SimpleTest.waitForExplicitFinish();
|
||||
SimpleTest.waitForFocus(function () {
|
||||
let iter = createContentIterator();
|
||||
|
||||
/**
|
||||
* Basic behavior tests of first(), last(), prev() and next() after initialized with an empty element.
|
||||
*/
|
||||
document.body.innerHTML = "<div></div>";
|
||||
let description = "Initialized with empty <div> as root node:";
|
||||
iter.initWithRootNode(Ci.nsIScriptableContentIterator.PRE_ORDER_ITERATOR, document.body.firstChild);
|
||||
is(SpecialPowers.unwrap(iter.currentNode), document.body.firstChild,
|
||||
`${description} currentNode should be the <div> immediately after initialization (got: ${getNodeDescription(iter.currentNode)})`);
|
||||
ok(!iter.isDone, `${description} isDone shouldn't be true immediately after initialization`);
|
||||
|
||||
iter.first();
|
||||
is(SpecialPowers.unwrap(iter.currentNode), document.body.firstChild,
|
||||
`${description} currentNode should be the <div> after calling first() (got: ${getNodeDescription(iter.currentNode)})`);
|
||||
ok(!iter.isDone, `${description} isDone shouldn't be true after calling first()`);
|
||||
|
||||
iter.last();
|
||||
is(SpecialPowers.unwrap(iter.currentNode), document.body.firstChild,
|
||||
`${description} currentNode should be the <div> after calling last() (got: ${getNodeDescription(iter.currentNode)})`);
|
||||
ok(!iter.isDone, `${description} isDone shouldn't be true after calling last()`);
|
||||
|
||||
iter.prev();
|
||||
is(SpecialPowers.unwrap(iter.currentNode), null,
|
||||
`${description} currentNode should be null after calling prev() (got: ${getNodeDescription(iter.currentNode)})`);
|
||||
ok(iter.isDone, `${description} isDone should be true after calling prev()`); // XXX Is this expected?
|
||||
|
||||
iter.first();
|
||||
is(SpecialPowers.unwrap(iter.currentNode), document.body.firstChild,
|
||||
`${description} currentNode should be the <div> after calling first() even after once done (got: ${getNodeDescription(iter.currentNode)})`);
|
||||
ok(!iter.isDone, `${description} isDone shouldn't be true after calling first() even after once done`);
|
||||
|
||||
iter.next();
|
||||
is(SpecialPowers.unwrap(iter.currentNode), null,
|
||||
`${description} currentNode should be null after calling next() (got: ${getNodeDescription(iter.currentNode)})`);
|
||||
ok(iter.isDone, `${description} isDone should be true after calling next()`);
|
||||
|
||||
/**
|
||||
* Basic behavior tests of first(), last(), prev() and next() after initialized with a range which selects empty element.
|
||||
*/
|
||||
let range = document.createRange();
|
||||
range.selectNode(document.body.firstChild);
|
||||
description = "Initialized with range including only empty <div>:";
|
||||
iter.initWithRange(Ci.nsIScriptableContentIterator.PRE_ORDER_ITERATOR, range);
|
||||
is(SpecialPowers.unwrap(iter.currentNode), document.body.firstChild,
|
||||
`${description} currentNode should be the <div> immediately after initialization (got: ${getNodeDescription(iter.currentNode)})`);
|
||||
ok(!iter.isDone, `${description} isDone shouldn't be true immediately after initialization`);
|
||||
|
||||
iter.first();
|
||||
is(SpecialPowers.unwrap(iter.currentNode), document.body.firstChild,
|
||||
`${description} currentNode should be the <div> after calling first() (got: ${getNodeDescription(iter.currentNode)})`);
|
||||
ok(!iter.isDone, `${description} isDone shouldn't be true after calling first()`);
|
||||
|
||||
iter.last();
|
||||
is(SpecialPowers.unwrap(iter.currentNode), document.body.firstChild,
|
||||
`${description} currentNode should be the <div> after calling last() (got: ${getNodeDescription(iter.currentNode)})`);
|
||||
ok(!iter.isDone, `${description} isDone shouldn't be true after calling last()`);
|
||||
|
||||
iter.prev();
|
||||
is(SpecialPowers.unwrap(iter.currentNode), null,
|
||||
`${description} currentNode should be null after calling prev() (got: ${getNodeDescription(iter.currentNode)})`);
|
||||
ok(iter.isDone, `${description} isDone should be true after calling prev()`); // XXX Is this expected?
|
||||
|
||||
iter.first();
|
||||
is(SpecialPowers.unwrap(iter.currentNode), document.body.firstChild,
|
||||
`${description} currentNode should be the <div> after calling first() even after once done (got: ${getNodeDescription(iter.currentNode)})`);
|
||||
ok(!iter.isDone, `${description} isDone shouldn't be true after calling first() even after once done`);
|
||||
|
||||
iter.next();
|
||||
is(SpecialPowers.unwrap(iter.currentNode), null,
|
||||
`${description} currentNode should be null after calling next() (got: ${getNodeDescription(iter.currentNode)})`);
|
||||
ok(iter.isDone, `${description} isDone should be true after calling next()`);
|
||||
|
||||
/**
|
||||
* Basic behavior tests of first(), last(), prev() and next() after initialized with positions which select empty element.
|
||||
*/
|
||||
range.selectNode(document.body.firstChild);
|
||||
description = "Initialized with positions including only empty <div>:";
|
||||
iter.initWithPositions(Ci.nsIScriptableContentIterator.PRE_ORDER_ITERATOR,
|
||||
range.startContainer, range.startOffset,
|
||||
range.endContainer, range.endOffset);
|
||||
is(SpecialPowers.unwrap(iter.currentNode), document.body.firstChild,
|
||||
`${description} currentNode should be the <div> immediately after initialization (got: ${getNodeDescription(iter.currentNode)})`);
|
||||
ok(!iter.isDone, `${description} isDone shouldn't be true immediately after initialization`);
|
||||
|
||||
iter.first();
|
||||
is(SpecialPowers.unwrap(iter.currentNode), document.body.firstChild,
|
||||
`${description} currentNode should be the <div> after calling first() (got: ${getNodeDescription(iter.currentNode)})`);
|
||||
ok(!iter.isDone, `${description} isDone shouldn't be true after calling first()`);
|
||||
|
||||
iter.last();
|
||||
is(SpecialPowers.unwrap(iter.currentNode), document.body.firstChild,
|
||||
`${description} currentNode should be the <div> after calling last() (got: ${getNodeDescription(iter.currentNode)})`);
|
||||
ok(!iter.isDone, `${description} isDone shouldn't be true after calling last()`);
|
||||
|
||||
iter.prev();
|
||||
is(SpecialPowers.unwrap(iter.currentNode), null,
|
||||
`${description} currentNode should be null after calling prev() (got: ${getNodeDescription(iter.currentNode)})`);
|
||||
ok(iter.isDone, `${description} isDone should be true after calling prev()`); // XXX Is this expected?
|
||||
|
||||
iter.first();
|
||||
is(SpecialPowers.unwrap(iter.currentNode), document.body.firstChild,
|
||||
`${description} currentNode should be the <div> after calling first() even after once done (got: ${getNodeDescription(iter.currentNode)})`);
|
||||
ok(!iter.isDone, `${description} isDone shouldn't be true after calling first() even after once done`);
|
||||
|
||||
iter.next();
|
||||
is(SpecialPowers.unwrap(iter.currentNode), null,
|
||||
`${description} currentNode should be null after calling next() (got: ${getNodeDescription(iter.currentNode)})`);
|
||||
ok(iter.isDone, `${description} isDone should be true after calling next()`);
|
||||
|
||||
/**
|
||||
* Tests to initializing with collapsed range in an empty element.
|
||||
*/
|
||||
range = document.createRange();
|
||||
range.collapse(document.body.firstChild, 0);
|
||||
description = "Initialized with range collapsed in empty <div>:";
|
||||
iter.initWithRange(Ci.nsIScriptableContentIterator.PRE_ORDER_ITERATOR, range);
|
||||
is(SpecialPowers.unwrap(iter.currentNode), null,
|
||||
`${description} currentNode should be null immediately after initialization (got: ${getNodeDescription(iter.currentNode)})`);
|
||||
ok(iter.isDone, `${description} isDone should be true immediately after initialization`);
|
||||
|
||||
iter.first();
|
||||
is(SpecialPowers.unwrap(iter.currentNode), null,
|
||||
`${description} currentNode should be null even after calling first() (got: ${getNodeDescription(iter.currentNode)})`);
|
||||
ok(iter.isDone, `${description} isDone should be true even after calling first()`);
|
||||
|
||||
/**
|
||||
* Tests to initializing with collapsed range in an empty element.
|
||||
*/
|
||||
description = "Initialized with a position in empty <div>:";
|
||||
iter.initWithPositions(Ci.nsIScriptableContentIterator.PRE_ORDER_ITERATOR,
|
||||
document.body.firstChild, 0, document.body.firstChild, 0);
|
||||
is(SpecialPowers.unwrap(iter.currentNode), null,
|
||||
`${description} currentNode should be null immediately after initialization (got: ${getNodeDescription(iter.currentNode)})`);
|
||||
ok(iter.isDone, `${description} isDone should be true immediately after initialization`);
|
||||
|
||||
iter.first();
|
||||
is(SpecialPowers.unwrap(iter.currentNode), null,
|
||||
`${description} currentNode should be null even after calling first() (got: ${getNodeDescription(iter.currentNode)})`);
|
||||
ok(iter.isDone, `${description} isDone should be true even after calling first()`);
|
||||
|
||||
/**
|
||||
* Basic behavior tests of first(), last(), prev() and next() after initialized with the text element.
|
||||
*/
|
||||
document.body.innerHTML = "<div>some text.</div>";
|
||||
description = "Initialized with a text node as root node:";
|
||||
iter.initWithRootNode(Ci.nsIScriptableContentIterator.PRE_ORDER_ITERATOR, document.body.firstChild.firstChild);
|
||||
is(SpecialPowers.unwrap(iter.currentNode), document.body.firstChild.firstChild,
|
||||
`${description} currentNode should be the text node immediately after initialization (got: ${getNodeDescription(iter.currentNode)})`);
|
||||
ok(!iter.isDone, `${description} isDone shouldn't be true immediately after initialization`);
|
||||
|
||||
iter.first();
|
||||
is(SpecialPowers.unwrap(iter.currentNode), document.body.firstChild.firstChild,
|
||||
`${description} currentNode should be the text node after calling first() (got: ${getNodeDescription(iter.currentNode)})`);
|
||||
ok(!iter.isDone, `${description} isDone shouldn't be the text node after calling first()`);
|
||||
|
||||
iter.last();
|
||||
is(SpecialPowers.unwrap(iter.currentNode), document.body.firstChild.firstChild,
|
||||
`${description} currentNode should be the text node after calling last() (got: ${getNodeDescription(iter.currentNode)})`);
|
||||
ok(!iter.isDone, `${description} isDone shouldn't be true after calling last()`);
|
||||
|
||||
iter.prev();
|
||||
is(SpecialPowers.unwrap(iter.currentNode), null,
|
||||
`${description} currentNode should be null after calling prev() (got: ${getNodeDescription(iter.currentNode)})`);
|
||||
ok(iter.isDone, `${description} isDone should be true after calling prev()`); // XXX Is this expected?
|
||||
|
||||
iter.first();
|
||||
is(SpecialPowers.unwrap(iter.currentNode), document.body.firstChild.firstChild,
|
||||
`${description} currentNode should be the text node after calling first() even after once done (got: ${getNodeDescription(iter.currentNode)})`);
|
||||
ok(!iter.isDone, `${description} isDone shouldn't be true after calling first() even after once done`);
|
||||
|
||||
iter.next();
|
||||
is(SpecialPowers.unwrap(iter.currentNode), null,
|
||||
`${description} currentNode should be null after calling next() (got: ${getNodeDescription(iter.currentNode)})`);
|
||||
ok(iter.isDone, `${description} isDone should be true after calling next()`);
|
||||
|
||||
/**
|
||||
* Basic behavior tests of first(), last(), prev() and next() after initialized with a range which selects the text node.
|
||||
*/
|
||||
range = document.createRange();
|
||||
range.selectNode(document.body.firstChild.firstChild);
|
||||
description = "Initialized with range including only text node:";
|
||||
iter.initWithRange(Ci.nsIScriptableContentIterator.PRE_ORDER_ITERATOR, range);
|
||||
is(SpecialPowers.unwrap(iter.currentNode), document.body.firstChild.firstChild,
|
||||
`${description} currentNode should be the text node immediately after initialization (got: ${getNodeDescription(iter.currentNode)})`);
|
||||
ok(!iter.isDone, `${description} isDone shouldn't be true immediately after initialization`);
|
||||
|
||||
iter.first();
|
||||
is(SpecialPowers.unwrap(iter.currentNode), document.body.firstChild.firstChild,
|
||||
`${description} currentNode should be the text node after calling first() (got: ${getNodeDescription(iter.currentNode)})`);
|
||||
ok(!iter.isDone, `${description} isDone shouldn't be true after calling first()`);
|
||||
|
||||
iter.last();
|
||||
is(SpecialPowers.unwrap(iter.currentNode), document.body.firstChild.firstChild,
|
||||
`${description} currentNode should be the text node after calling last() (got: ${getNodeDescription(iter.currentNode)})`);
|
||||
ok(!iter.isDone, `${description} isDone shouldn't be true after calling last()`);
|
||||
|
||||
iter.prev();
|
||||
is(SpecialPowers.unwrap(iter.currentNode), null,
|
||||
`${description} currentNode should be null after calling prev() (got: ${getNodeDescription(iter.currentNode)})`);
|
||||
ok(iter.isDone, `${description} isDone should be true after calling prev()`); // XXX Is this expected?
|
||||
|
||||
iter.first();
|
||||
is(SpecialPowers.unwrap(iter.currentNode), document.body.firstChild.firstChild,
|
||||
`${description} currentNode should be the text node after calling first() even after once done (got: ${getNodeDescription(iter.currentNode)})`);
|
||||
ok(!iter.isDone, `${description} isDone shouldn't be true after calling first() even after once done`);
|
||||
|
||||
iter.next();
|
||||
is(SpecialPowers.unwrap(iter.currentNode), null,
|
||||
`${description} currentNode should be null after calling next() (got: ${getNodeDescription(iter.currentNode)})`);
|
||||
ok(iter.isDone, `${description} isDone should be true after calling next()`);
|
||||
|
||||
/**
|
||||
* Basic behavior tests of first() and next() after initialized with positions which select the text node.
|
||||
* XXX In this case, content iterator lists up the parent <div> element. Not sure if this is intentional difference
|
||||
* from initWithRange().
|
||||
*/
|
||||
range.selectNode(document.body.firstChild);
|
||||
description = "Initialized with positions including only text node:";
|
||||
iter.initWithPositions(Ci.nsIScriptableContentIterator.PRE_ORDER_ITERATOR,
|
||||
range.startContainer, range.startOffset,
|
||||
range.endContainer, range.endOffset);
|
||||
is(SpecialPowers.unwrap(iter.currentNode), document.body.firstChild,
|
||||
`${description} currentNode should be the <div> element immediately after initialization (got: ${getNodeDescription(iter.currentNode)})`);
|
||||
ok(!iter.isDone, `${description} isDone shouldn't be true immediately after initialization`);
|
||||
|
||||
iter.first();
|
||||
is(SpecialPowers.unwrap(iter.currentNode), document.body.firstChild,
|
||||
`${description} currentNode should be the <div> element after calling first() (got: ${getNodeDescription(iter.currentNode)})`);
|
||||
ok(!iter.isDone, `${description} isDone shouldn't be true after calling first()`);
|
||||
|
||||
iter.next();
|
||||
is(SpecialPowers.unwrap(iter.currentNode), document.body.firstChild.firstChild,
|
||||
`${description} currentNode should be the text node after calling next() from first position (got: ${getNodeDescription(iter.currentNode)})`);
|
||||
ok(!iter.isDone, `${description} isDone shouldn't be true after calling next() from first position`);
|
||||
|
||||
iter.next();
|
||||
is(SpecialPowers.unwrap(iter.currentNode), null,
|
||||
`${description} currentNode should be null after calling next() from second position (got: ${getNodeDescription(iter.currentNode)})`);
|
||||
ok(iter.isDone, `${description} isDone should be true after calling next() from second position`);
|
||||
|
||||
/**
|
||||
* Tests to initializing with collapsed range at start of a text node.
|
||||
*/
|
||||
range = document.createRange();
|
||||
range.collapse(document.body.firstChild.firstChild, 0);
|
||||
description = "Initialized with range collapsed at start of text node:";
|
||||
iter.initWithRange(Ci.nsIScriptableContentIterator.PRE_ORDER_ITERATOR, range);
|
||||
is(SpecialPowers.unwrap(iter.currentNode), null,
|
||||
`${description} currentNode should be null immediately after initialization (got: ${getNodeDescription(iter.currentNode)})`);
|
||||
ok(iter.isDone, `${description} isDone should be true immediately after initialization`);
|
||||
|
||||
iter.first();
|
||||
is(SpecialPowers.unwrap(iter.currentNode), null,
|
||||
`${description} currentNode should be null even after calling first() (got: ${getNodeDescription(iter.currentNode)})`);
|
||||
ok(iter.isDone, `${description} isDone should be true even after calling first()`);
|
||||
|
||||
/**
|
||||
* Tests to initializing with collapsed range at start of a text node.
|
||||
* XXX In this case, content iterator lists up the text node. Not sure if this is intentional difference
|
||||
* from initWithRange().
|
||||
*/
|
||||
description = "Initialized with a position at start of text node:";
|
||||
iter.initWithPositions(Ci.nsIScriptableContentIterator.PRE_ORDER_ITERATOR,
|
||||
document.body.firstChild.firstChild, 0, document.body.firstChild.firstChild, 0);
|
||||
is(SpecialPowers.unwrap(iter.currentNode), document.body.firstChild.firstChild,
|
||||
`${description} currentNode should be the text node immediately after initialization (got: ${getNodeDescription(iter.currentNode)})`);
|
||||
ok(!iter.isDone, `${description} isDone shouldn't be true immediately after initialization`);
|
||||
|
||||
iter.next();
|
||||
is(SpecialPowers.unwrap(iter.currentNode), null,
|
||||
`${description} currentNode should be null after calling next() (got: ${getNodeDescription(iter.currentNode)})`);
|
||||
ok(iter.isDone, `${description} isDone should be true after calling next()`);
|
||||
|
||||
/**
|
||||
* Tests to initializing with collapsed range at end of a text node.
|
||||
*/
|
||||
range = document.createRange();
|
||||
range.collapse(document.body.firstChild.firstChild, document.body.firstChild.firstChild.length);
|
||||
description = "Initialized with range collapsed at end of text node:";
|
||||
iter.initWithRange(Ci.nsIScriptableContentIterator.PRE_ORDER_ITERATOR, range);
|
||||
is(SpecialPowers.unwrap(iter.currentNode), null,
|
||||
`${description} currentNode should be null immediately after initialization (got: ${getNodeDescription(iter.currentNode)})`);
|
||||
ok(iter.isDone, `${description} isDone should be true immediately after initialization`);
|
||||
|
||||
iter.first();
|
||||
is(SpecialPowers.unwrap(iter.currentNode), null,
|
||||
`${description} currentNode should be null even after calling first() (got: ${getNodeDescription(iter.currentNode)})`);
|
||||
ok(iter.isDone, `${description} isDone should be true even after calling first()`);
|
||||
|
||||
/**
|
||||
* Tests to initializing with collapsed range at end of a text node.
|
||||
* XXX In this case, content iterator lists up the text node. Not sure if this is intentional difference
|
||||
* from initWithRange().
|
||||
*/
|
||||
description = "Initialized with a position at end of text node:";
|
||||
iter.initWithPositions(Ci.nsIScriptableContentIterator.PRE_ORDER_ITERATOR,
|
||||
document.body.firstChild.firstChild, document.body.firstChild.firstChild.length,
|
||||
document.body.firstChild.firstChild, document.body.firstChild.firstChild.length);
|
||||
is(SpecialPowers.unwrap(iter.currentNode), document.body.firstChild.firstChild,
|
||||
`${description} currentNode should be the text node immediately after initialization (got: ${getNodeDescription(iter.currentNode)})`);
|
||||
ok(!iter.isDone, `${description} isDone shouldn't be true immediately after initialization`);
|
||||
|
||||
iter.next();
|
||||
is(SpecialPowers.unwrap(iter.currentNode), null,
|
||||
`${description} currentNode should be null after calling next() (got: ${getNodeDescription(iter.currentNode)})`);
|
||||
ok(iter.isDone, `${description} isDone should be true after calling next()`);
|
||||
|
||||
/**
|
||||
* Tests to initializing with collapsed range at middle of a text node.
|
||||
*/
|
||||
range = document.createRange();
|
||||
range.collapse(document.body.firstChild.firstChild, document.body.firstChild.firstChild.length / 2);
|
||||
description = "Initialized with range collapsed at end of text node:";
|
||||
iter.initWithRange(Ci.nsIScriptableContentIterator.PRE_ORDER_ITERATOR, range);
|
||||
is(SpecialPowers.unwrap(iter.currentNode), null,
|
||||
`${description} currentNode should be null immediately after initialization (got: ${getNodeDescription(iter.currentNode)})`);
|
||||
ok(iter.isDone, `${description} isDone should be true immediately after initialization`);
|
||||
|
||||
iter.first();
|
||||
is(SpecialPowers.unwrap(iter.currentNode), null,
|
||||
`${description} currentNode should be null even after calling first() (got: ${getNodeDescription(iter.currentNode)})`);
|
||||
ok(iter.isDone, `${description} isDone should be true even after calling first()`);
|
||||
|
||||
/**
|
||||
* Tests to initializing with collapsed range at middle of a text node.
|
||||
* XXX In this case, content iterator lists up the text node. Not sure if this is intentional difference
|
||||
* from initWithRange().
|
||||
*/
|
||||
description = "Initialized with a position at end of text node:";
|
||||
iter.initWithPositions(Ci.nsIScriptableContentIterator.PRE_ORDER_ITERATOR,
|
||||
document.body.firstChild.firstChild, document.body.firstChild.firstChild.length / 2,
|
||||
document.body.firstChild.firstChild, document.body.firstChild.firstChild.length / 2);
|
||||
is(SpecialPowers.unwrap(iter.currentNode), document.body.firstChild.firstChild,
|
||||
`${description} currentNode should be the text node immediately after initialization (got: ${getNodeDescription(iter.currentNode)})`);
|
||||
ok(!iter.isDone, `${description} isDone shouldn't be true immediately after initialization`);
|
||||
|
||||
iter.next();
|
||||
is(SpecialPowers.unwrap(iter.currentNode), null,
|
||||
`${description} currentNode should be null after calling next() (got: ${getNodeDescription(iter.currentNode)})`);
|
||||
ok(iter.isDone, `${description} isDone should be true after calling next()`);
|
||||
|
||||
/**
|
||||
* Tests to initializing with a range selecting all text in a text node.
|
||||
*/
|
||||
range = document.createRange();
|
||||
range.setStart(document.body.firstChild.firstChild, 0);
|
||||
range.setEnd(document.body.firstChild.firstChild, document.body.firstChild.firstChild.length);
|
||||
description = "Initialized with range selecting all text in text node:";
|
||||
iter.initWithRange(Ci.nsIScriptableContentIterator.PRE_ORDER_ITERATOR, range);
|
||||
is(SpecialPowers.unwrap(iter.currentNode), document.body.firstChild.firstChild,
|
||||
`${description} currentNode should be the text node immediately after initialization (got: ${getNodeDescription(iter.currentNode)})`);
|
||||
ok(!iter.isDone, `${description} isDone shouldn't be true immediately after initialization`);
|
||||
|
||||
iter.next();
|
||||
is(SpecialPowers.unwrap(iter.currentNode), null,
|
||||
`${description} currentNode should be null after calling next() (got: ${getNodeDescription(iter.currentNode)})`);
|
||||
ok(iter.isDone, `${description} isDone should be true after calling next()`);
|
||||
|
||||
/**
|
||||
* Tests to initializing with positions selecting all text in a text node.
|
||||
*/
|
||||
description = "Initialized with positions selecting all text in text node:";
|
||||
iter.initWithPositions(Ci.nsIScriptableContentIterator.PRE_ORDER_ITERATOR,
|
||||
document.body.firstChild.firstChild, 0,
|
||||
document.body.firstChild.firstChild, document.body.firstChild.firstChild.length);
|
||||
is(SpecialPowers.unwrap(iter.currentNode), document.body.firstChild.firstChild,
|
||||
`${description} currentNode should be the text node immediately after initialization (got: ${getNodeDescription(iter.currentNode)})`);
|
||||
ok(!iter.isDone, `${description} isDone shouldn't be true immediately after initialization`);
|
||||
|
||||
iter.next();
|
||||
is(SpecialPowers.unwrap(iter.currentNode), null,
|
||||
`${description} currentNode should be null after calling next() (got: ${getNodeDescription(iter.currentNode)})`);
|
||||
ok(iter.isDone, `${description} isDone should be true after calling next()`);
|
||||
|
||||
/**
|
||||
* Basic tests with complicated tree.
|
||||
*/
|
||||
function check(aIter, aExpectedResult, aDescription) {
|
||||
if (aExpectedResult.length > 0) {
|
||||
is(SpecialPowers.unwrap(aIter.currentNode), aExpectedResult[0],
|
||||
`${aDescription}: currentNode should be the text node immediately after initialization (got: ${getNodeDescription(aIter.currentNode)}, expected: ${getNodeDescription(aExpectedResult[0])})`);
|
||||
ok(!aIter.isDone, `${aDescription}: isDone shouldn't be true immediately after initialization`);
|
||||
|
||||
aIter.first();
|
||||
is(SpecialPowers.unwrap(aIter.currentNode), aExpectedResult[0],
|
||||
`${aDescription}: currentNode should be the text node after calling first() (got: ${getNodeDescription(aIter.currentNode)}, expected: ${getNodeDescription(aExpectedResult[0])})`);
|
||||
ok(!aIter.isDone, `${aDescription}: isDone shouldn't be true after calling first()`);
|
||||
|
||||
for (let expected of aExpectedResult) {
|
||||
is(SpecialPowers.unwrap(aIter.currentNode), expected,
|
||||
`${aDescription}: currentNode should be the node (got: ${getNodeDescription(aIter.currentNode)}, expected: ${getNodeDescription(expected)})`);
|
||||
ok(!aIter.isDone, `${aDescription}: isDone shouldn't be true when ${getNodeDescription(expected)} is expected`);
|
||||
aIter.next();
|
||||
}
|
||||
|
||||
is(SpecialPowers.unwrap(aIter.currentNode), null,
|
||||
`${aDescription}: currentNode should be null after calling next() finally (got: ${getNodeDescription(aIter.currentNode)}`);
|
||||
ok(aIter.isDone, `${aDescription}: isDone should be true after calling next() finally`);
|
||||
} else {
|
||||
is(SpecialPowers.unwrap(aIter.currentNode), null,
|
||||
`${aDescription}: currentNode should be null immediately after initialization (got: ${getNodeDescription(aIter.currentNode)})`);
|
||||
ok(aIter.isDone, `${aDescription}: isDone should be true immediately after initialization`);
|
||||
|
||||
aIter.first();
|
||||
is(SpecialPowers.unwrap(aIter.currentNode), null,
|
||||
`${aDescription}: currentNode should be null after calling first() (got: ${getNodeDescription(aIter.currentNode)})`);
|
||||
ok(aIter.isDone, `${aDescription}: isDone should be true after calling first()`);
|
||||
}
|
||||
}
|
||||
|
||||
document.body.innerHTML = "<p>" +
|
||||
"Here is <b>bold</b> and <i><u>underlined and </u>italic </i><span>or no style text.</span><br>" +
|
||||
"</p>" +
|
||||
"<p>" +
|
||||
"Here is an <input> element: <input type=\"text\" value=\"default value\"><br>\n" +
|
||||
"and a <textarea> element: <textarea>text area's text node</textarea><br><br>\n" +
|
||||
"<!-- and here is comment node -->" +
|
||||
"</p>";
|
||||
|
||||
let expectedResult =
|
||||
[document.body, // <body>
|
||||
document.body.firstChild, // first <p>
|
||||
document.body.firstChild.firstChild, // the first text node
|
||||
document.body.firstChild.firstChild.nextSibling, // <b>
|
||||
document.body.firstChild.firstChild.nextSibling.firstChild, // text in <b>
|
||||
document.body.firstChild.firstChild.nextSibling.nextSibling, // text next to <b>
|
||||
document.body.firstChild.firstChild.nextSibling.nextSibling.nextSibling, // <i>
|
||||
document.body.firstChild.firstChild.nextSibling.nextSibling.nextSibling.firstChild, // <u>
|
||||
document.body.firstChild.firstChild.nextSibling.nextSibling.nextSibling.firstChild.firstChild, // text in <u>
|
||||
document.body.firstChild.firstChild.nextSibling.nextSibling.nextSibling.firstChild.nextSibling, // text next to <u>
|
||||
document.body.firstChild.firstChild.nextSibling.nextSibling.nextSibling.nextSibling, // <span>
|
||||
document.body.firstChild.firstChild.nextSibling.nextSibling.nextSibling.nextSibling.firstChild, // text in <span>
|
||||
document.body.firstChild.firstChild.nextSibling.nextSibling.nextSibling.nextSibling.nextSibling, // <br> next to <span>
|
||||
document.body.firstChild.nextSibling, // second <p>
|
||||
document.body.firstChild.nextSibling.firstChild, // the first text node in second <p>
|
||||
document.body.firstChild.nextSibling.firstChild.nextSibling, // <input>
|
||||
document.body.firstChild.nextSibling.firstChild.nextSibling.nextSibling, // <br> next to <input>
|
||||
document.body.firstChild.nextSibling.firstChild.nextSibling.nextSibling.nextSibling, // text next to <input>
|
||||
document.body.firstChild.nextSibling.firstChild.nextSibling.nextSibling.nextSibling.nextSibling, // <textarea>
|
||||
document.body.firstChild.nextSibling.firstChild.nextSibling.nextSibling.nextSibling.nextSibling.firstChild, // text in <textarea>
|
||||
document.body.firstChild.nextSibling.firstChild.nextSibling.nextSibling.nextSibling.nextSibling.nextSibling, // <br> next to <textarea>
|
||||
document.body.firstChild.nextSibling.firstChild.nextSibling.nextSibling.nextSibling.nextSibling.nextSibling.nextSibling, // <br> next to <br>
|
||||
document.body.firstChild.nextSibling.firstChild.nextSibling.nextSibling.nextSibling.nextSibling.nextSibling.nextSibling.nextSibling, // text next to <br>
|
||||
document.body.firstChild.nextSibling.firstChild.nextSibling.nextSibling.nextSibling.nextSibling.nextSibling.nextSibling.nextSibling.nextSibling] // comment
|
||||
|
||||
iter.initWithRootNode(Ci.nsIScriptableContentIterator.PRE_ORDER_ITERATOR, document.body);
|
||||
check(iter, expectedResult, "Initialized with the <body> as root element");
|
||||
|
||||
/**
|
||||
* Selects the <body> with a range.
|
||||
*/
|
||||
range = document.createRange();
|
||||
range.selectNode(document.body);
|
||||
iter.initWithRange(Ci.nsIScriptableContentIterator.PRE_ORDER_ITERATOR, range);
|
||||
check(iter, expectedResult, "Initialized with range selecting the <body>");
|
||||
|
||||
/**
|
||||
* Selects the <body> with positions.
|
||||
*/
|
||||
iter.initWithPositions(Ci.nsIScriptableContentIterator.PRE_ORDER_ITERATOR,
|
||||
range.startContainer, range.startOffset, range.endContainer, range.endOffset);
|
||||
check(iter, expectedResult, "Initialized with positions selecting the <body>");
|
||||
|
||||
/**
|
||||
* Selects all children in the <body> with a range.
|
||||
*/
|
||||
expectedResult.shift(); // <body> shouldn't be listed up.
|
||||
range = document.createRange();
|
||||
range.selectNodeContents(document.body);
|
||||
iter.initWithRange(Ci.nsIScriptableContentIterator.PRE_ORDER_ITERATOR, range);
|
||||
check(iter, expectedResult, "Initialized with range selecting all children in the <body>");
|
||||
|
||||
/**
|
||||
* Selects all children in the <body> with positions.
|
||||
*/
|
||||
iter.initWithPositions(Ci.nsIScriptableContentIterator.PRE_ORDER_ITERATOR,
|
||||
range.startContainer, range.startOffset, range.endContainer, range.endOffset);
|
||||
check(iter, expectedResult, "Initialized with positions selecting all children in the <body>");
|
||||
|
||||
/**
|
||||
* range/positions around elements.
|
||||
*/
|
||||
document.body.innerHTML = "abc<b>def</b><i>ghi</i>jkl";
|
||||
range = document.createRange();
|
||||
|
||||
range.setStart(document.body.firstChild, 0);
|
||||
range.setEnd(document.body.firstChild.nextSibling.firstChild, 2);
|
||||
iter.initWithRange(Ci.nsIScriptableContentIterator.PRE_ORDER_ITERATOR, range);
|
||||
check(iter,
|
||||
[document.body.firstChild, // text before <b>
|
||||
document.body.firstChild.nextSibling, // <b>
|
||||
document.body.firstChild.nextSibling.firstChild], // text in <b>
|
||||
"Initialized with range selecting '[abc<b>de]f'");
|
||||
iter.initWithPositions(Ci.nsIScriptableContentIterator.PRE_ORDER_ITERATOR,
|
||||
range.startContainer, range.startOffset,
|
||||
range.endContainer, range.endOffset);
|
||||
check(iter,
|
||||
[document.body.firstChild, // text before <b>
|
||||
document.body.firstChild.nextSibling, // <b>
|
||||
document.body.firstChild.nextSibling.firstChild], // text in <b>
|
||||
"Initialized with positions selecting '[abc<b>de]f'");
|
||||
|
||||
range.setStart(document.body.firstChild, 2);
|
||||
range.setEnd(document.body.firstChild.nextSibling.firstChild, 2);
|
||||
iter.initWithRange(Ci.nsIScriptableContentIterator.PRE_ORDER_ITERATOR, range);
|
||||
check(iter,
|
||||
[document.body.firstChild, // text before <b>
|
||||
document.body.firstChild.nextSibling, // <b>
|
||||
document.body.firstChild.nextSibling.firstChild], // text in <b>
|
||||
"Initialized with range selecting 'ab[c<b>de]f'");
|
||||
iter.initWithPositions(Ci.nsIScriptableContentIterator.PRE_ORDER_ITERATOR,
|
||||
range.startContainer, range.startOffset,
|
||||
range.endContainer, range.endOffset);
|
||||
check(iter,
|
||||
[document.body.firstChild, // text before <b>
|
||||
document.body.firstChild.nextSibling, // <b>
|
||||
document.body.firstChild.nextSibling.firstChild], // text in <b>
|
||||
"Initialized with positions selecting 'ab[c<b>de]f'");
|
||||
|
||||
range.setStart(document.body.firstChild, 3);
|
||||
range.setEnd(document.body.firstChild.nextSibling.firstChild, 2);
|
||||
iter.initWithRange(Ci.nsIScriptableContentIterator.PRE_ORDER_ITERATOR, range);
|
||||
check(iter,
|
||||
[document.body.firstChild, // text before <b>
|
||||
document.body.firstChild.nextSibling, // <b>
|
||||
document.body.firstChild.nextSibling.firstChild], // text in <b>
|
||||
"Initialized with range selecting 'abc[<b>de]f'");
|
||||
iter.initWithPositions(Ci.nsIScriptableContentIterator.PRE_ORDER_ITERATOR,
|
||||
range.startContainer, range.startOffset,
|
||||
range.endContainer, range.endOffset);
|
||||
check(iter,
|
||||
[document.body.firstChild, // text before <b>
|
||||
document.body.firstChild.nextSibling, // <b>
|
||||
document.body.firstChild.nextSibling.firstChild], // text in <b>
|
||||
"Initialized with positions selecting 'abc[<b>de]f'");
|
||||
|
||||
range.setStart(document.body, 1);
|
||||
range.setEnd(document.body.firstChild.nextSibling.firstChild, 2);
|
||||
iter.initWithRange(Ci.nsIScriptableContentIterator.PRE_ORDER_ITERATOR, range);
|
||||
check(iter,
|
||||
[document.body.firstChild.nextSibling, // <b>
|
||||
document.body.firstChild.nextSibling.firstChild], // text in <b>
|
||||
"Initialized with range selecting 'abc{<b>de]f'");
|
||||
iter.initWithPositions(Ci.nsIScriptableContentIterator.PRE_ORDER_ITERATOR,
|
||||
range.startContainer, range.startOffset,
|
||||
range.endContainer, range.endOffset);
|
||||
check(iter,
|
||||
[document.body.firstChild.nextSibling, // <b>
|
||||
document.body.firstChild.nextSibling.firstChild], // text in <b>
|
||||
"Initialized with positions selecting 'abc{<b>de]f'");
|
||||
|
||||
range.setStart(document.body.firstChild.nextSibling, 0);
|
||||
range.setEnd(document.body.firstChild.nextSibling.firstChild, 2);
|
||||
iter.initWithRange(Ci.nsIScriptableContentIterator.PRE_ORDER_ITERATOR, range);
|
||||
check(iter,
|
||||
[document.body.firstChild.nextSibling.firstChild], // text in <b>
|
||||
"Initialized with range selecting '<b>{de]f'");
|
||||
iter.initWithPositions(Ci.nsIScriptableContentIterator.PRE_ORDER_ITERATOR,
|
||||
range.startContainer, range.startOffset,
|
||||
range.endContainer, range.endOffset);
|
||||
check(iter,
|
||||
[document.body.firstChild.nextSibling.firstChild], // text in <b>
|
||||
"Initialized with positions selecting '<b>{de]f'");
|
||||
|
||||
range.setStart(document.body.firstChild.nextSibling, 0);
|
||||
range.setEnd(document.body.firstChild.nextSibling.firstChild, 3);
|
||||
iter.initWithRange(Ci.nsIScriptableContentIterator.PRE_ORDER_ITERATOR, range);
|
||||
check(iter,
|
||||
[document.body.firstChild.nextSibling.firstChild], // text in <b>
|
||||
"Initialized with range selecting '<b>{def]</b>'");
|
||||
iter.initWithPositions(Ci.nsIScriptableContentIterator.PRE_ORDER_ITERATOR,
|
||||
range.startContainer, range.startOffset,
|
||||
range.endContainer, range.endOffset);
|
||||
check(iter,
|
||||
[document.body.firstChild.nextSibling.firstChild], // text in <b>
|
||||
"Initialized with positions selecting '<b>{def]</b>'");
|
||||
|
||||
range.setStart(document.body.firstChild.nextSibling, 0);
|
||||
range.setEnd(document.body.firstChild.nextSibling, 1);
|
||||
iter.initWithRange(Ci.nsIScriptableContentIterator.PRE_ORDER_ITERATOR, range);
|
||||
check(iter,
|
||||
[document.body.firstChild.nextSibling.firstChild], // text in <b>
|
||||
"Initialized with range selecting '<b>{def}</b>'");
|
||||
iter.initWithPositions(Ci.nsIScriptableContentIterator.PRE_ORDER_ITERATOR,
|
||||
range.startContainer, range.startOffset,
|
||||
range.endContainer, range.endOffset);
|
||||
check(iter,
|
||||
[document.body.firstChild.nextSibling.firstChild], // text in <b>
|
||||
"Initialized with positions selecting '<b>{def}</b>'");
|
||||
|
||||
range.setStart(document.body.firstChild.nextSibling, 0);
|
||||
range.setEnd(document.body, 2);
|
||||
iter.initWithRange(Ci.nsIScriptableContentIterator.PRE_ORDER_ITERATOR, range);
|
||||
check(iter,
|
||||
[document.body.firstChild.nextSibling.firstChild], // text in <b>
|
||||
"Initialized with range selecting '<b>{def</b>}<i>ghi'");
|
||||
iter.initWithPositions(Ci.nsIScriptableContentIterator.PRE_ORDER_ITERATOR,
|
||||
range.startContainer, range.startOffset,
|
||||
range.endContainer, range.endOffset);
|
||||
check(iter,
|
||||
[document.body.firstChild.nextSibling.firstChild], // text in <b>
|
||||
"Initialized with positions selecting '<b>{def</b>}<i>ghi'");
|
||||
|
||||
range.setStart(document.body.firstChild.nextSibling.firstChild, 3);
|
||||
range.setEnd(document.body, 2);
|
||||
iter.initWithRange(Ci.nsIScriptableContentIterator.PRE_ORDER_ITERATOR, range);
|
||||
check(iter,
|
||||
[document.body.firstChild.nextSibling.firstChild], // text in <b>
|
||||
"Initialized with range selecting '<b>def[</b>}<i>ghi'");
|
||||
iter.initWithPositions(Ci.nsIScriptableContentIterator.PRE_ORDER_ITERATOR,
|
||||
range.startContainer, range.startOffset,
|
||||
range.endContainer, range.endOffset);
|
||||
check(iter,
|
||||
[document.body.firstChild.nextSibling.firstChild], // text in <b>
|
||||
"Initialized with positions selecting '<b>def[</b>}<i>ghi'");
|
||||
|
||||
range.setStart(document.body.firstChild.nextSibling, 1);
|
||||
range.setEnd(document.body, 2);
|
||||
iter.initWithRange(Ci.nsIScriptableContentIterator.PRE_ORDER_ITERATOR, range);
|
||||
check(iter, [],
|
||||
"Initialized with range selecting '<b>def{</b>}<i>ghi'");
|
||||
iter.initWithPositions(Ci.nsIScriptableContentIterator.PRE_ORDER_ITERATOR,
|
||||
range.startContainer, range.startOffset,
|
||||
range.endContainer, range.endOffset);
|
||||
check(iter, [],
|
||||
"Initialized with positions selecting '<b>def{</b>}<i>ghi'");
|
||||
|
||||
range.setStart(document.body.firstChild.nextSibling, 1);
|
||||
range.setEnd(document.body.firstChild.nextSibling.nextSibling, 0);
|
||||
iter.initWithRange(Ci.nsIScriptableContentIterator.PRE_ORDER_ITERATOR, range);
|
||||
check(iter,
|
||||
[document.body.firstChild.nextSibling.nextSibling], // <i>
|
||||
"Initialized with range selecting '<b>def{</b><i>}ghi'");
|
||||
iter.initWithPositions(Ci.nsIScriptableContentIterator.PRE_ORDER_ITERATOR,
|
||||
range.startContainer, range.startOffset,
|
||||
range.endContainer, range.endOffset);
|
||||
check(iter,
|
||||
[document.body.firstChild.nextSibling.nextSibling], // <i>
|
||||
"Initialized with positions selecting '<b>def{</b><i>}ghi'");
|
||||
|
||||
range.setStart(document.body.firstChild.nextSibling, 1);
|
||||
range.setEnd(document.body.firstChild.nextSibling.nextSibling.firstChild, 0);
|
||||
iter.initWithRange(Ci.nsIScriptableContentIterator.PRE_ORDER_ITERATOR, range);
|
||||
check(iter,
|
||||
[document.body.firstChild.nextSibling.nextSibling, // <i>
|
||||
document.body.firstChild.nextSibling.nextSibling.firstChild], // text in <i>
|
||||
"Initialized with range selecting '<b>def{</b><i>]ghi'");
|
||||
iter.initWithPositions(Ci.nsIScriptableContentIterator.PRE_ORDER_ITERATOR,
|
||||
range.startContainer, range.startOffset,
|
||||
range.endContainer, range.endOffset);
|
||||
check(iter,
|
||||
[document.body.firstChild.nextSibling.nextSibling, // <i>
|
||||
document.body.firstChild.nextSibling.nextSibling.firstChild], // text in <i>
|
||||
"Initialized with positions selecting '<b>def{</b><i>]ghi'");
|
||||
|
||||
range.setStart(document.body.firstChild.nextSibling.nextSibling, 0);
|
||||
range.setEnd(document.body, 3);
|
||||
iter.initWithRange(Ci.nsIScriptableContentIterator.PRE_ORDER_ITERATOR, range);
|
||||
check(iter,
|
||||
[document.body.firstChild.nextSibling.nextSibling.firstChild], // text in <i>
|
||||
"Initialized with range selecting '<i>{ghi</i>}jkl'");
|
||||
iter.initWithPositions(Ci.nsIScriptableContentIterator.PRE_ORDER_ITERATOR,
|
||||
range.startContainer, range.startOffset,
|
||||
range.endContainer, range.endOffset);
|
||||
check(iter,
|
||||
[document.body.firstChild.nextSibling.nextSibling.firstChild], // text in <i>
|
||||
"Initialized with positions selecting '<i>{ghi</i>}jkl'");
|
||||
|
||||
range.setStart(document.body.firstChild.nextSibling.nextSibling.firstChild, 3);
|
||||
range.setEnd(document.body, 3);
|
||||
iter.initWithRange(Ci.nsIScriptableContentIterator.PRE_ORDER_ITERATOR, range);
|
||||
check(iter,
|
||||
[document.body.firstChild.nextSibling.nextSibling.firstChild], // text in <i>
|
||||
"Initialized with range selecting '<i>ghi[</i>}jkl'");
|
||||
iter.initWithPositions(Ci.nsIScriptableContentIterator.PRE_ORDER_ITERATOR,
|
||||
range.startContainer, range.startOffset,
|
||||
range.endContainer, range.endOffset);
|
||||
check(iter,
|
||||
[document.body.firstChild.nextSibling.nextSibling.firstChild], // text in <i>
|
||||
"Initialized with positions selecting '<i>ghi[</i>}jkl'");
|
||||
|
||||
range.setStart(document.body.firstChild.nextSibling.nextSibling, 1);
|
||||
range.setEnd(document.body, 3);
|
||||
iter.initWithRange(Ci.nsIScriptableContentIterator.PRE_ORDER_ITERATOR, range);
|
||||
check(iter, [],
|
||||
"Initialized with range selecting '<i>ghi{</i>}jkl'");
|
||||
iter.initWithPositions(Ci.nsIScriptableContentIterator.PRE_ORDER_ITERATOR,
|
||||
range.startContainer, range.startOffset,
|
||||
range.endContainer, range.endOffset);
|
||||
check(iter, [],
|
||||
"Initialized with positions selecting '<i>ghi{</i>}jkl'");
|
||||
|
||||
range.setStart(document.body.firstChild.nextSibling.nextSibling, 1);
|
||||
range.setEnd(document.body.firstChild.nextSibling.nextSibling.nextSibling, 0);
|
||||
iter.initWithRange(Ci.nsIScriptableContentIterator.PRE_ORDER_ITERATOR, range);
|
||||
check(iter,
|
||||
[document.body.firstChild.nextSibling.nextSibling.nextSibling], // text after <i>
|
||||
"Initialized with range selecting '<i>ghi{</i>]jkl'");
|
||||
iter.initWithPositions(Ci.nsIScriptableContentIterator.PRE_ORDER_ITERATOR,
|
||||
range.startContainer, range.startOffset,
|
||||
range.endContainer, range.endOffset);
|
||||
check(iter,
|
||||
[document.body.firstChild.nextSibling.nextSibling.nextSibling], // text after <i>
|
||||
"Initialized with positions selecting '<i>ghi{</i>]jkl'");
|
||||
|
||||
/**
|
||||
* range/positions around <br> elements.
|
||||
*/
|
||||
document.body.innerHTML = "abc<br>def";
|
||||
range = document.createRange();
|
||||
range.setStart(document.body.firstChild, 3);
|
||||
range.setEnd(document.body.firstChild.nextSibling.nextSibling, 0);
|
||||
iter.initWithRange(Ci.nsIScriptableContentIterator.PRE_ORDER_ITERATOR, range);
|
||||
check(iter,
|
||||
[document.body.firstChild, // text before <br>
|
||||
document.body.firstChild.nextSibling, // <br>
|
||||
document.body.firstChild.nextSibling.nextSibling], // text after <br>
|
||||
"Initialized with range selecting 'abc[<br>]def'");
|
||||
iter.initWithPositions(Ci.nsIScriptableContentIterator.PRE_ORDER_ITERATOR,
|
||||
range.startContainer, range.startOffset,
|
||||
range.endContainer, range.endOffset);
|
||||
check(iter,
|
||||
[document.body.firstChild, // text before <br>
|
||||
document.body.firstChild.nextSibling, // <br>
|
||||
document.body.firstChild.nextSibling.nextSibling], // text after <br>
|
||||
"Initialized with positions selecting 'abc[<br>]def'");
|
||||
|
||||
range.setStart(document.body, 1);
|
||||
range.setEnd(document.body.firstChild.nextSibling.nextSibling, 0);
|
||||
iter.initWithRange(Ci.nsIScriptableContentIterator.PRE_ORDER_ITERATOR, range);
|
||||
check(iter,
|
||||
[document.body.firstChild.nextSibling, // <br>
|
||||
document.body.firstChild.nextSibling.nextSibling], // text after <br>
|
||||
"Initialized with range selecting 'abc{<br>]def'");
|
||||
iter.initWithPositions(Ci.nsIScriptableContentIterator.PRE_ORDER_ITERATOR,
|
||||
range.startContainer, range.startOffset,
|
||||
range.endContainer, range.endOffset);
|
||||
check(iter,
|
||||
[document.body.firstChild.nextSibling, // <br>
|
||||
document.body.firstChild.nextSibling.nextSibling], // text after <br>
|
||||
"Initialized with positions selecting 'abc{<br>]def'");
|
||||
|
||||
range.setStart(document.body.firstChild.nextSibling, 0);
|
||||
range.setEnd(document.body.firstChild.nextSibling.nextSibling, 0);
|
||||
iter.initWithRange(Ci.nsIScriptableContentIterator.PRE_ORDER_ITERATOR, range);
|
||||
check(iter,
|
||||
[document.body.firstChild.nextSibling, // <br>
|
||||
document.body.firstChild.nextSibling.nextSibling], // text after <br>
|
||||
"Initialized with range selecting 'abc{<br>]def' (starting in <br>)");
|
||||
iter.initWithPositions(Ci.nsIScriptableContentIterator.PRE_ORDER_ITERATOR,
|
||||
range.startContainer, range.startOffset,
|
||||
range.endContainer, range.endOffset);
|
||||
check(iter,
|
||||
[document.body.firstChild.nextSibling, // <br>
|
||||
document.body.firstChild.nextSibling.nextSibling], // text after <br>
|
||||
"Initialized with positions selecting 'abc{<br>]def' (starting in <br>)");
|
||||
|
||||
range.setStart(document.body, 1);
|
||||
range.setEnd(document.body, 2);
|
||||
iter.initWithRange(Ci.nsIScriptableContentIterator.PRE_ORDER_ITERATOR, range);
|
||||
check(iter,
|
||||
[document.body.firstChild.nextSibling], // <br>
|
||||
"Initialized with range selecting 'abc{<br>}def'");
|
||||
iter.initWithPositions(Ci.nsIScriptableContentIterator.PRE_ORDER_ITERATOR,
|
||||
range.startContainer, range.startOffset,
|
||||
range.endContainer, range.endOffset);
|
||||
check(iter,
|
||||
[document.body.firstChild.nextSibling], // <br>
|
||||
"Initialized with positions selecting 'abc{<br>}def'");
|
||||
|
||||
range.setStart(document.body.firstChild, 3);
|
||||
range.setEnd(document.body.firstChild.nextSibling, 0);
|
||||
iter.initWithRange(Ci.nsIScriptableContentIterator.PRE_ORDER_ITERATOR, range);
|
||||
check(iter,
|
||||
[document.body.firstChild], // text before <br>
|
||||
"Initialized with range selecting 'abc[}<br>def' (ending in <br>)");
|
||||
iter.initWithPositions(Ci.nsIScriptableContentIterator.PRE_ORDER_ITERATOR,
|
||||
range.startContainer, range.startOffset,
|
||||
range.endContainer, range.endOffset);
|
||||
check(iter,
|
||||
[document.body.firstChild], // text before <br>
|
||||
"Initialized with positions selecting 'abc[}<br>def' (ending in <br>)");
|
||||
|
||||
finish();
|
||||
});
|
||||
</script>
|
||||
</head>
|
||||
<body></body>
|
||||
</html>
|
|
@ -0,0 +1,690 @@
|
|||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>Test for content subtree iterator</title>
|
||||
<script src="/tests/SimpleTest/SimpleTest.js"></script>
|
||||
<link rel="stylesheet" href="/tests/SimpleTest/test.css">
|
||||
<script>
|
||||
var Cc = SpecialPowers.Cc;
|
||||
var Ci = SpecialPowers.Ci;
|
||||
function finish() {
|
||||
// The SimpleTest may require usual elements in the template, but they shouldn't be during test.
|
||||
// So, let's create them at end of the test.
|
||||
document.body.innerHTML = '<div id="display"></div><div id="content"></div><pre id="test"></pre>';
|
||||
SimpleTest.finish();
|
||||
}
|
||||
|
||||
function createContentIterator() {
|
||||
return Cc["@mozilla.org/scriptable-content-iterator;1"]
|
||||
.createInstance(Ci.nsIScriptableContentIterator);
|
||||
}
|
||||
|
||||
function getNodeDescription(aNode) {
|
||||
if (aNode === undefined) {
|
||||
return "undefine";
|
||||
}
|
||||
if (aNode === null) {
|
||||
return "null";
|
||||
}
|
||||
function getElementDescription(aElement) {
|
||||
if (aElement.tagName === "BR") {
|
||||
if (aElement.previousSibling) {
|
||||
return `<br> element after ${getNodeDescription(aElement.previousSibling)}`;
|
||||
}
|
||||
return `<br> element in ${getElementDescription(aElement.parentElement)}`;
|
||||
}
|
||||
let hasHint = aElement == document.body;
|
||||
let tag = `<${aElement.tagName.toLowerCase()}`;
|
||||
if (aElement.getAttribute("id")) {
|
||||
tag += ` id="${aElement.getAttribute("id")}"`;
|
||||
hasHint = true;
|
||||
}
|
||||
if (aElement.getAttribute("class")) {
|
||||
tag += ` class="${aElement.getAttribute("class")}"`;
|
||||
hasHint = true;
|
||||
}
|
||||
if (aElement.getAttribute("type")) {
|
||||
tag += ` type="${aElement.getAttribute("type")}"`;
|
||||
}
|
||||
if (aElement.getAttribute("name")) {
|
||||
tag += ` name="${aElement.getAttribute("name")}"`;
|
||||
}
|
||||
if (aElement.getAttribute("value")) {
|
||||
tag += ` value="${aElement.getAttribute("value")}"`;
|
||||
hasHint = true;
|
||||
}
|
||||
if (aElement.getAttribute("style")) {
|
||||
tag += ` style="${aElement.getAttribute("style")}"`;
|
||||
hasHint = true;
|
||||
}
|
||||
if (hasHint) {
|
||||
return tag + ">";
|
||||
}
|
||||
return `${tag}> in ${getElementDescription(aElement.parentElement)}`;
|
||||
}
|
||||
switch (aNode.nodeType) {
|
||||
case aNode.TEXT_NODE:
|
||||
return `text node, "${aNode.wholeText.replace(/\n/g, '\\n')}"`;
|
||||
case aNode.COMMENT_NODE:
|
||||
return `comment node, "${aNode.data.replace(/\n/g, '\\n')}"`;
|
||||
case aNode.ELEMENT_NODE:
|
||||
return getElementDescription(SpecialPowers.unwrap(aNode));
|
||||
default:
|
||||
return "unknown node";
|
||||
}
|
||||
}
|
||||
|
||||
SimpleTest.waitForExplicitFinish();
|
||||
SimpleTest.waitForFocus(function () {
|
||||
let iter = createContentIterator();
|
||||
|
||||
/**
|
||||
* FYI: nsContentSubtreeIterator does not support initWithRootNode() nor positionAt().
|
||||
*/
|
||||
|
||||
/**
|
||||
* Basic behavior tests of first(), last(), prev() and next() after initialized with a range which selects empty element.
|
||||
*/
|
||||
document.body.innerHTML = "<div></div>";
|
||||
let range = document.createRange();
|
||||
range.selectNode(document.body.firstChild);
|
||||
let description = "Initialized with range including only empty <div>:";
|
||||
iter.initWithRange(Ci.nsIScriptableContentIterator.SUBTREE_ITERATOR, range);
|
||||
is(SpecialPowers.unwrap(iter.currentNode), document.body.firstChild,
|
||||
`${description} currentNode should be the <div> immediately after initialization (got: ${getNodeDescription(iter.currentNode)})`);
|
||||
ok(!iter.isDone, `${description} isDone shouldn't be true immediately after initialization`);
|
||||
|
||||
iter.first();
|
||||
is(SpecialPowers.unwrap(iter.currentNode), document.body.firstChild,
|
||||
`${description} currentNode should be the <div> after calling first() (got: ${getNodeDescription(iter.currentNode)})`);
|
||||
ok(!iter.isDone, `${description} isDone shouldn't be true after calling first()`);
|
||||
|
||||
iter.last();
|
||||
is(SpecialPowers.unwrap(iter.currentNode), document.body.firstChild,
|
||||
`${description} currentNode should be the <div> after calling last() (got: ${getNodeDescription(iter.currentNode)})`);
|
||||
ok(!iter.isDone, `${description} isDone shouldn't be true after calling last()`);
|
||||
|
||||
iter.prev();
|
||||
is(SpecialPowers.unwrap(iter.currentNode), null,
|
||||
`${description} currentNode should be null after calling prev() (got: ${getNodeDescription(iter.currentNode)})`);
|
||||
ok(iter.isDone, `${description} isDone should be true after calling prev()`); // XXX Is this expected?
|
||||
|
||||
iter.first();
|
||||
is(SpecialPowers.unwrap(iter.currentNode), document.body.firstChild,
|
||||
`${description} currentNode should be the <div> after calling first() even after once done (got: ${getNodeDescription(iter.currentNode)})`);
|
||||
ok(!iter.isDone, `${description} isDone shouldn't be true after calling first() even after once done`);
|
||||
|
||||
iter.next();
|
||||
is(SpecialPowers.unwrap(iter.currentNode), null,
|
||||
`${description} currentNode should be null after calling next() (got: ${getNodeDescription(iter.currentNode)})`);
|
||||
ok(iter.isDone, `${description} isDone should be true after calling next()`);
|
||||
|
||||
/**
|
||||
* Basic behavior tests of first(), last(), prev() and next() after initialized with positions which select empty element.
|
||||
*/
|
||||
range.selectNode(document.body.firstChild);
|
||||
description = "Initialized with positions including only empty <div>:";
|
||||
iter.initWithPositions(Ci.nsIScriptableContentIterator.SUBTREE_ITERATOR,
|
||||
range.startContainer, range.startOffset,
|
||||
range.endContainer, range.endOffset);
|
||||
is(SpecialPowers.unwrap(iter.currentNode), document.body.firstChild,
|
||||
`${description} currentNode should be the <div> immediately after initialization (got: ${getNodeDescription(iter.currentNode)})`);
|
||||
ok(!iter.isDone, `${description} isDone shouldn't be true immediately after initialization`);
|
||||
|
||||
iter.first();
|
||||
is(SpecialPowers.unwrap(iter.currentNode), document.body.firstChild,
|
||||
`${description} currentNode should be the <div> after calling first() (got: ${getNodeDescription(iter.currentNode)})`);
|
||||
ok(!iter.isDone, `${description} isDone shouldn't be true after calling first()`);
|
||||
|
||||
iter.last();
|
||||
is(SpecialPowers.unwrap(iter.currentNode), document.body.firstChild,
|
||||
`${description} currentNode should be the <div> after calling last() (got: ${getNodeDescription(iter.currentNode)})`);
|
||||
ok(!iter.isDone, `${description} isDone shouldn't be true after calling last()`);
|
||||
|
||||
iter.prev();
|
||||
is(SpecialPowers.unwrap(iter.currentNode), null,
|
||||
`${description} currentNode should be null after calling prev() (got: ${getNodeDescription(iter.currentNode)})`);
|
||||
ok(iter.isDone, `${description} isDone should be true after calling prev()`); // XXX Is this expected?
|
||||
|
||||
iter.first();
|
||||
is(SpecialPowers.unwrap(iter.currentNode), document.body.firstChild,
|
||||
`${description} currentNode should be the <div> after calling first() even after once done (got: ${getNodeDescription(iter.currentNode)})`);
|
||||
ok(!iter.isDone, `${description} isDone shouldn't be true after calling first() even after once done`);
|
||||
|
||||
iter.next();
|
||||
is(SpecialPowers.unwrap(iter.currentNode), null,
|
||||
`${description} currentNode should be null after calling next() (got: ${getNodeDescription(iter.currentNode)})`);
|
||||
ok(iter.isDone, `${description} isDone should be true after calling next()`);
|
||||
|
||||
/**
|
||||
* Tests to initializing with collapsed range in an empty element.
|
||||
*/
|
||||
range = document.createRange();
|
||||
range.collapse(document.body.firstChild, 0);
|
||||
description = "Initialized with range collapsed in empty <div>:";
|
||||
iter.initWithRange(Ci.nsIScriptableContentIterator.SUBTREE_ITERATOR, range);
|
||||
is(SpecialPowers.unwrap(iter.currentNode), null,
|
||||
`${description} currentNode should be null immediately after initialization (got: ${getNodeDescription(iter.currentNode)})`);
|
||||
ok(iter.isDone, `${description} isDone should be true immediately after initialization`);
|
||||
|
||||
iter.first();
|
||||
is(SpecialPowers.unwrap(iter.currentNode), null,
|
||||
`${description} currentNode should be null even after calling first() (got: ${getNodeDescription(iter.currentNode)})`);
|
||||
ok(iter.isDone, `${description} isDone should be true even after calling first()`);
|
||||
|
||||
/**
|
||||
* Tests to initializing with collapsed range in an empty element.
|
||||
*/
|
||||
description = "Initialized with a position in empty <div>:";
|
||||
iter.initWithPositions(Ci.nsIScriptableContentIterator.SUBTREE_ITERATOR,
|
||||
document.body.firstChild, 0, document.body.firstChild, 0);
|
||||
is(SpecialPowers.unwrap(iter.currentNode), null,
|
||||
`${description} currentNode should be null immediately after initialization (got: ${getNodeDescription(iter.currentNode)})`);
|
||||
ok(iter.isDone, `${description} isDone should be true immediately after initialization`);
|
||||
|
||||
iter.first();
|
||||
is(SpecialPowers.unwrap(iter.currentNode), null,
|
||||
`${description} currentNode should be null even after calling first() (got: ${getNodeDescription(iter.currentNode)})`);
|
||||
ok(iter.isDone, `${description} isDone should be true even after calling first()`);
|
||||
|
||||
/**
|
||||
* Basic behavior tests of first(), last(), prev() and next() after initialized with a range which selects the text node.
|
||||
*/
|
||||
document.body.innerHTML = "<div>some text.</div>";
|
||||
range = document.createRange();
|
||||
range.selectNode(document.body.firstChild.firstChild);
|
||||
description = "Initialized with range including only text node:";
|
||||
iter.initWithRange(Ci.nsIScriptableContentIterator.SUBTREE_ITERATOR, range);
|
||||
is(SpecialPowers.unwrap(iter.currentNode), document.body.firstChild.firstChild,
|
||||
`${description} currentNode should be the text node immediately after initialization (got: ${getNodeDescription(iter.currentNode)})`);
|
||||
ok(!iter.isDone, `${description} isDone shouldn't be true immediately after initialization`);
|
||||
|
||||
iter.first();
|
||||
is(SpecialPowers.unwrap(iter.currentNode), document.body.firstChild.firstChild,
|
||||
`${description} currentNode should be the text node after calling first() (got: ${getNodeDescription(iter.currentNode)})`);
|
||||
ok(!iter.isDone, `${description} isDone shouldn't be true after calling first()`);
|
||||
|
||||
iter.last();
|
||||
is(SpecialPowers.unwrap(iter.currentNode), document.body.firstChild.firstChild,
|
||||
`${description} currentNode should be the text node after calling last() (got: ${getNodeDescription(iter.currentNode)})`);
|
||||
ok(!iter.isDone, `${description} isDone shouldn't be true after calling last()`);
|
||||
|
||||
iter.prev();
|
||||
is(SpecialPowers.unwrap(iter.currentNode), null,
|
||||
`${description} currentNode should be null after calling prev() (got: ${getNodeDescription(iter.currentNode)})`);
|
||||
ok(iter.isDone, `${description} isDone should be true after calling prev()`); // XXX Is this expected?
|
||||
|
||||
iter.first();
|
||||
is(SpecialPowers.unwrap(iter.currentNode), document.body.firstChild.firstChild,
|
||||
`${description} currentNode should be the text node after calling first() even after once done (got: ${getNodeDescription(iter.currentNode)})`);
|
||||
ok(!iter.isDone, `${description} isDone shouldn't be true after calling first() even after once done`);
|
||||
|
||||
iter.next();
|
||||
is(SpecialPowers.unwrap(iter.currentNode), null,
|
||||
`${description} currentNode should be null after calling next() (got: ${getNodeDescription(iter.currentNode)})`);
|
||||
ok(iter.isDone, `${description} isDone should be true after calling next()`);
|
||||
|
||||
/**
|
||||
* Basic behavior tests of first() and next() after initialized with positions which select the text node.
|
||||
* XXX In this case, content iterator lists up the parent <div> element. Not sure if this is intentional difference
|
||||
* from initWithRange().
|
||||
*/
|
||||
range.selectNode(document.body.firstChild);
|
||||
description = "Initialized with positions including only text node:";
|
||||
iter.initWithPositions(Ci.nsIScriptableContentIterator.SUBTREE_ITERATOR,
|
||||
range.startContainer, range.startOffset,
|
||||
range.endContainer, range.endOffset);
|
||||
is(SpecialPowers.unwrap(iter.currentNode), document.body.firstChild,
|
||||
`${description} currentNode should be the <div> element immediately after initialization (got: ${getNodeDescription(iter.currentNode)})`);
|
||||
ok(!iter.isDone, `${description} isDone shouldn't be true immediately after initialization`);
|
||||
|
||||
iter.first();
|
||||
is(SpecialPowers.unwrap(iter.currentNode), document.body.firstChild,
|
||||
`${description} currentNode should be the <div> element after calling first() (got: ${getNodeDescription(iter.currentNode)})`);
|
||||
ok(!iter.isDone, `${description} isDone shouldn't be true after calling first()`);
|
||||
|
||||
iter.next();
|
||||
is(SpecialPowers.unwrap(iter.currentNode), null,
|
||||
`${description} currentNode should be null after calling next() from first position (got: ${getNodeDescription(iter.currentNode)})`);
|
||||
ok(iter.isDone, `${description} isDone should be true after calling next() from first position`);
|
||||
|
||||
/**
|
||||
* Tests to initializing with collapsed range at start of a text node.
|
||||
*/
|
||||
range = document.createRange();
|
||||
range.collapse(document.body.firstChild.firstChild, 0);
|
||||
description = "Initialized with range collapsed at start of text node:";
|
||||
iter.initWithRange(Ci.nsIScriptableContentIterator.SUBTREE_ITERATOR, range);
|
||||
is(SpecialPowers.unwrap(iter.currentNode), null,
|
||||
`${description} currentNode should be null immediately after initialization (got: ${getNodeDescription(iter.currentNode)})`);
|
||||
ok(iter.isDone, `${description} isDone should be true immediately after initialization`);
|
||||
|
||||
iter.first();
|
||||
is(SpecialPowers.unwrap(iter.currentNode), null,
|
||||
`${description} currentNode should be null even after calling first() (got: ${getNodeDescription(iter.currentNode)})`);
|
||||
ok(iter.isDone, `${description} isDone should be true even after calling first()`);
|
||||
|
||||
/**
|
||||
* Tests to initializing with collapsed range at start of a text node.
|
||||
*/
|
||||
description = "Initialized with a position at start of text node:";
|
||||
iter.initWithPositions(Ci.nsIScriptableContentIterator.SUBTREE_ITERATOR,
|
||||
document.body.firstChild.firstChild, 0, document.body.firstChild.firstChild, 0);
|
||||
is(SpecialPowers.unwrap(iter.currentNode), null,
|
||||
`${description} currentNode should be null immediately after initialization (got: ${getNodeDescription(iter.currentNode)})`);
|
||||
ok(iter.isDone, `${description} isDone should be true immediately after initialization`);
|
||||
|
||||
iter.first();
|
||||
is(SpecialPowers.unwrap(iter.currentNode), null,
|
||||
`${description} currentNode should be null even after calling first() (got: ${getNodeDescription(iter.currentNode)})`);
|
||||
ok(iter.isDone, `${description} isDone should be true even after calling first()`);
|
||||
|
||||
/**
|
||||
* Tests to initializing with collapsed range at end of a text node.
|
||||
*/
|
||||
range = document.createRange();
|
||||
range.collapse(document.body.firstChild.firstChild, document.body.firstChild.firstChild.length);
|
||||
description = "Initialized with range collapsed at end of text node:";
|
||||
iter.initWithRange(Ci.nsIScriptableContentIterator.SUBTREE_ITERATOR, range);
|
||||
is(SpecialPowers.unwrap(iter.currentNode), null,
|
||||
`${description} currentNode should be null immediately after initialization (got: ${getNodeDescription(iter.currentNode)})`);
|
||||
ok(iter.isDone, `${description} isDone should be true immediately after initialization`);
|
||||
|
||||
iter.first();
|
||||
is(SpecialPowers.unwrap(iter.currentNode), null,
|
||||
`${description} currentNode should be null even after calling first() (got: ${getNodeDescription(iter.currentNode)})`);
|
||||
ok(iter.isDone, `${description} isDone should be true even after calling first()`);
|
||||
|
||||
/**
|
||||
* Tests to initializing with collapsed range at end of a text node.
|
||||
*/
|
||||
description = "Initialized with a position at end of text node:";
|
||||
iter.initWithPositions(Ci.nsIScriptableContentIterator.SUBTREE_ITERATOR,
|
||||
document.body.firstChild.firstChild, document.body.firstChild.firstChild.length,
|
||||
document.body.firstChild.firstChild, document.body.firstChild.firstChild.length);
|
||||
is(SpecialPowers.unwrap(iter.currentNode), null,
|
||||
`${description} currentNode should be null immediately after initialization (got: ${getNodeDescription(iter.currentNode)})`);
|
||||
ok(iter.isDone, `${description} isDone should be true immediately after initialization`);
|
||||
|
||||
iter.first();
|
||||
is(SpecialPowers.unwrap(iter.currentNode), null,
|
||||
`${description} currentNode should be null even after calling first() (got: ${getNodeDescription(iter.currentNode)})`);
|
||||
ok(iter.isDone, `${description} isDone should be true even after calling first()`);
|
||||
|
||||
/**
|
||||
* Tests to initializing with collapsed range at middle of a text node.
|
||||
*/
|
||||
range = document.createRange();
|
||||
range.collapse(document.body.firstChild.firstChild, document.body.firstChild.firstChild.length / 2);
|
||||
description = "Initialized with range collapsed at end of text node:";
|
||||
iter.initWithRange(Ci.nsIScriptableContentIterator.SUBTREE_ITERATOR, range);
|
||||
is(SpecialPowers.unwrap(iter.currentNode), null,
|
||||
`${description} currentNode should be null immediately after initialization (got: ${getNodeDescription(iter.currentNode)})`);
|
||||
ok(iter.isDone, `${description} isDone should be true immediately after initialization`);
|
||||
|
||||
iter.first();
|
||||
is(SpecialPowers.unwrap(iter.currentNode), null,
|
||||
`${description} currentNode should be null even after calling first() (got: ${getNodeDescription(iter.currentNode)})`);
|
||||
ok(iter.isDone, `${description} isDone should be true even after calling first()`);
|
||||
|
||||
/**
|
||||
* Tests to initializing with collapsed range at middle of a text node.
|
||||
*/
|
||||
description = "Initialized with a position at end of text node:";
|
||||
iter.initWithPositions(Ci.nsIScriptableContentIterator.SUBTREE_ITERATOR,
|
||||
document.body.firstChild.firstChild, document.body.firstChild.firstChild.length / 2,
|
||||
document.body.firstChild.firstChild, document.body.firstChild.firstChild.length / 2);
|
||||
is(SpecialPowers.unwrap(iter.currentNode), null,
|
||||
`${description} currentNode should be null immediately after initialization (got: ${getNodeDescription(iter.currentNode)})`);
|
||||
ok(iter.isDone, `${description} isDone should be true immediately after initialization`);
|
||||
|
||||
iter.first();
|
||||
is(SpecialPowers.unwrap(iter.currentNode), null,
|
||||
`${description} currentNode should be null even after calling first() (got: ${getNodeDescription(iter.currentNode)})`);
|
||||
ok(iter.isDone, `${description} isDone should be true even after calling first()`);
|
||||
|
||||
/**
|
||||
* Tests to initializing with a range selecting all text in a text node.
|
||||
*/
|
||||
range = document.createRange();
|
||||
range.setStart(document.body.firstChild.firstChild, 0);
|
||||
range.setEnd(document.body.firstChild.firstChild, document.body.firstChild.firstChild.length);
|
||||
description = "Initialized with range selecting all text in text node:";
|
||||
iter.initWithRange(Ci.nsIScriptableContentIterator.SUBTREE_ITERATOR, range);
|
||||
is(SpecialPowers.unwrap(iter.currentNode), null,
|
||||
`${description} currentNode should be null immediately after initialization (got: ${getNodeDescription(iter.currentNode)})`);
|
||||
ok(iter.isDone, `${description} isDone should be true immediately after initialization`);
|
||||
|
||||
iter.first();
|
||||
is(SpecialPowers.unwrap(iter.currentNode), null,
|
||||
`${description} currentNode should be null even after calling first() (got: ${getNodeDescription(iter.currentNode)})`);
|
||||
ok(iter.isDone, `${description} isDone should be true even after calling first()`);
|
||||
|
||||
/**
|
||||
* Tests to initializing with positions selecting all text in a text node.
|
||||
*/
|
||||
description = "Initialized with positions selecting all text in text node:";
|
||||
iter.initWithPositions(Ci.nsIScriptableContentIterator.SUBTREE_ITERATOR,
|
||||
document.body.firstChild.firstChild, 0,
|
||||
document.body.firstChild.firstChild, document.body.firstChild.firstChild.length);
|
||||
is(SpecialPowers.unwrap(iter.currentNode), null,
|
||||
`${description} currentNode should be null immediately after initialization (got: ${getNodeDescription(iter.currentNode)})`);
|
||||
ok(iter.isDone, `${description} isDone should be true immediately after initialization`);
|
||||
|
||||
iter.first();
|
||||
is(SpecialPowers.unwrap(iter.currentNode), null,
|
||||
`${description} currentNode should be null even after calling first() (got: ${getNodeDescription(iter.currentNode)})`);
|
||||
ok(iter.isDone, `${description} isDone should be true even after calling first()`);
|
||||
|
||||
/**
|
||||
* Basic tests with complicated tree.
|
||||
*/
|
||||
function check(aIter, aExpectedResult, aDescription) {
|
||||
if (aExpectedResult.length > 0) {
|
||||
is(SpecialPowers.unwrap(aIter.currentNode), aExpectedResult[0],
|
||||
`${aDescription}: currentNode should be the text node immediately after initialization (got: ${getNodeDescription(aIter.currentNode)}, expected: ${getNodeDescription(aExpectedResult[0])})`);
|
||||
ok(!aIter.isDone, `${aDescription}: isDone shouldn't be true immediately after initialization`);
|
||||
|
||||
aIter.first();
|
||||
is(SpecialPowers.unwrap(aIter.currentNode), aExpectedResult[0],
|
||||
`${aDescription}: currentNode should be the text node after calling first() (got: ${getNodeDescription(aIter.currentNode)}, expected: ${getNodeDescription(aExpectedResult[0])})`);
|
||||
ok(!aIter.isDone, `${aDescription}: isDone shouldn't be true after calling first()`);
|
||||
|
||||
for (let expected of aExpectedResult) {
|
||||
is(SpecialPowers.unwrap(aIter.currentNode), expected,
|
||||
`${aDescription}: currentNode should be the node (got: ${getNodeDescription(aIter.currentNode)}, expected: ${getNodeDescription(expected)})`);
|
||||
ok(!aIter.isDone, `${aDescription}: isDone shouldn't be true when ${getNodeDescription(expected)} is expected`);
|
||||
aIter.next();
|
||||
}
|
||||
|
||||
is(SpecialPowers.unwrap(aIter.currentNode), null,
|
||||
`${aDescription}: currentNode should be null after calling next() finally (got: ${getNodeDescription(aIter.currentNode)}`);
|
||||
ok(aIter.isDone, `${aDescription}: isDone should be true after calling next() finally`);
|
||||
} else {
|
||||
is(SpecialPowers.unwrap(aIter.currentNode), null,
|
||||
`${aDescription}: currentNode should be null immediately after initialization (got: ${getNodeDescription(aIter.currentNode)})`);
|
||||
ok(aIter.isDone, `${aDescription}: isDone should be true immediately after initialization`);
|
||||
|
||||
aIter.first();
|
||||
is(SpecialPowers.unwrap(aIter.currentNode), null,
|
||||
`${aDescription}: currentNode should be null after calling first() (got: ${getNodeDescription(aIter.currentNode)})`);
|
||||
ok(aIter.isDone, `${aDescription}: isDone should be true after calling first()`);
|
||||
}
|
||||
}
|
||||
|
||||
document.body.innerHTML = "<p>" +
|
||||
"Here is <b>bold</b> and <i><u>underlined and </u>italic </i><span>or no style text.</span><br>" +
|
||||
"</p>" +
|
||||
"<p>" +
|
||||
"Here is an <input> element: <input type=\"text\" value=\"default value\"><br>\n" +
|
||||
"and a <textarea> element: <textarea>text area's text node</textarea><br><br>\n" +
|
||||
"<!-- and here is comment node -->" +
|
||||
"</p>";
|
||||
|
||||
/**
|
||||
* Selects the <body> with a range.
|
||||
*/
|
||||
range = document.createRange();
|
||||
range.selectNode(document.body);
|
||||
iter.initWithRange(Ci.nsIScriptableContentIterator.SUBTREE_ITERATOR, range);
|
||||
check(iter, [document.body], "Initialized with range selecting the <body>");
|
||||
|
||||
/**
|
||||
* Selects the <body> with positions.
|
||||
*/
|
||||
iter.initWithPositions(Ci.nsIScriptableContentIterator.SUBTREE_ITERATOR,
|
||||
range.startContainer, range.startOffset, range.endContainer, range.endOffset);
|
||||
check(iter, [document.body], "Initialized with positions selecting the <body>");
|
||||
|
||||
/**
|
||||
* Selects all children in the <body> with a range.
|
||||
*/
|
||||
range = document.createRange();
|
||||
range.selectNodeContents(document.body);
|
||||
iter.initWithRange(Ci.nsIScriptableContentIterator.SUBTREE_ITERATOR, range);
|
||||
check(iter,
|
||||
[document.body.firstChild, // first <p>
|
||||
document.body.firstChild.nextSibling], // second <p>
|
||||
"Initialized with range selecting all children in the <body>");
|
||||
|
||||
/**
|
||||
* Selects all children in the <body> with positions.
|
||||
*/
|
||||
iter.initWithPositions(Ci.nsIScriptableContentIterator.SUBTREE_ITERATOR,
|
||||
range.startContainer, range.startOffset, range.endContainer, range.endOffset);
|
||||
check(iter,
|
||||
[document.body.firstChild, // first <p>
|
||||
document.body.firstChild.nextSibling], // second <p>
|
||||
"Initialized with positions selecting all children in the <body>");
|
||||
|
||||
/**
|
||||
* range/positions around elements.
|
||||
*/
|
||||
document.body.innerHTML = "abc<b>def</b><i>ghi</i>jkl";
|
||||
range = document.createRange();
|
||||
|
||||
range.setStart(document.body.firstChild, 0);
|
||||
range.setEnd(document.body.firstChild.nextSibling.firstChild, 2);
|
||||
iter.initWithRange(Ci.nsIScriptableContentIterator.SUBTREE_ITERATOR, range);
|
||||
check(iter, [], "Initialized with range selecting '[abc<b>de]f'");
|
||||
iter.initWithPositions(Ci.nsIScriptableContentIterator.SUBTREE_ITERATOR,
|
||||
range.startContainer, range.startOffset,
|
||||
range.endContainer, range.endOffset);
|
||||
check(iter, [], "Initialized with positions selecting '[abc<b>de]f'");
|
||||
|
||||
range.setStart(document.body.firstChild, 2);
|
||||
range.setEnd(document.body.firstChild.nextSibling.firstChild, 2);
|
||||
iter.initWithRange(Ci.nsIScriptableContentIterator.SUBTREE_ITERATOR, range);
|
||||
check(iter,[], "Initialized with range selecting 'ab[c<b>de]f'");
|
||||
iter.initWithPositions(Ci.nsIScriptableContentIterator.SUBTREE_ITERATOR,
|
||||
range.startContainer, range.startOffset,
|
||||
range.endContainer, range.endOffset);
|
||||
check(iter, [], "Initialized with positions selecting 'ab[c<b>de]f'");
|
||||
|
||||
range.setStart(document.body.firstChild, 3);
|
||||
range.setEnd(document.body.firstChild.nextSibling.firstChild, 2);
|
||||
iter.initWithRange(Ci.nsIScriptableContentIterator.SUBTREE_ITERATOR, range);
|
||||
check(iter, [], "Initialized with range selecting 'abc[<b>de]f'");
|
||||
iter.initWithPositions(Ci.nsIScriptableContentIterator.SUBTREE_ITERATOR,
|
||||
range.startContainer, range.startOffset,
|
||||
range.endContainer, range.endOffset);
|
||||
check(iter, [], "Initialized with positions selecting 'abc[<b>de]f'");
|
||||
|
||||
range.setStart(document.body, 1);
|
||||
range.setEnd(document.body.firstChild.nextSibling.firstChild, 2);
|
||||
iter.initWithRange(Ci.nsIScriptableContentIterator.SUBTREE_ITERATOR, range);
|
||||
check(iter, [], "Initialized with range selecting 'abc{<b>de]f'");
|
||||
iter.initWithPositions(Ci.nsIScriptableContentIterator.SUBTREE_ITERATOR,
|
||||
range.startContainer, range.startOffset,
|
||||
range.endContainer, range.endOffset);
|
||||
check(iter, [], "Initialized with positions selecting 'abc{<b>de]f'");
|
||||
|
||||
range.setStart(document.body.firstChild.nextSibling, 0);
|
||||
range.setEnd(document.body.firstChild.nextSibling.firstChild, 2);
|
||||
iter.initWithRange(Ci.nsIScriptableContentIterator.SUBTREE_ITERATOR, range);
|
||||
check(iter, [], "Initialized with range selecting '<b>{de]f'");
|
||||
iter.initWithPositions(Ci.nsIScriptableContentIterator.SUBTREE_ITERATOR,
|
||||
range.startContainer, range.startOffset,
|
||||
range.endContainer, range.endOffset);
|
||||
check(iter, [], "Initialized with positions selecting '<b>{de]f'");
|
||||
|
||||
range.setStart(document.body.firstChild.nextSibling, 0);
|
||||
range.setEnd(document.body.firstChild.nextSibling.firstChild, 3);
|
||||
iter.initWithRange(Ci.nsIScriptableContentIterator.SUBTREE_ITERATOR, range);
|
||||
check(iter, [], "Initialized with range selecting '<b>{def]</b>'");
|
||||
iter.initWithPositions(Ci.nsIScriptableContentIterator.SUBTREE_ITERATOR,
|
||||
range.startContainer, range.startOffset,
|
||||
range.endContainer, range.endOffset);
|
||||
check(iter, [], "Initialized with positions selecting '<b>{def]</b>'");
|
||||
|
||||
range.setStart(document.body.firstChild.nextSibling, 0);
|
||||
range.setEnd(document.body.firstChild.nextSibling, 1);
|
||||
iter.initWithRange(Ci.nsIScriptableContentIterator.SUBTREE_ITERATOR, range);
|
||||
check(iter,
|
||||
[document.body.firstChild.nextSibling.firstChild], // text in <b>
|
||||
"Initialized with range selecting '<b>{def}</b>'");
|
||||
iter.initWithPositions(Ci.nsIScriptableContentIterator.SUBTREE_ITERATOR,
|
||||
range.startContainer, range.startOffset,
|
||||
range.endContainer, range.endOffset);
|
||||
check(iter,
|
||||
[document.body.firstChild.nextSibling.firstChild], // text in <b>
|
||||
"Initialized with positions selecting '<b>{def}</b>'");
|
||||
|
||||
range.setStart(document.body.firstChild.nextSibling, 0);
|
||||
range.setEnd(document.body, 2);
|
||||
iter.initWithRange(Ci.nsIScriptableContentIterator.SUBTREE_ITERATOR, range);
|
||||
check(iter,
|
||||
[document.body.firstChild.nextSibling.firstChild], // text in <b>
|
||||
"Initialized with range selecting '<b>{def</b>}<i>ghi'");
|
||||
iter.initWithPositions(Ci.nsIScriptableContentIterator.SUBTREE_ITERATOR,
|
||||
range.startContainer, range.startOffset,
|
||||
range.endContainer, range.endOffset);
|
||||
check(iter,
|
||||
[document.body.firstChild.nextSibling.firstChild], // text in <b>
|
||||
"Initialized with positions selecting '<b>{def</b>}<i>ghi'");
|
||||
|
||||
range.setStart(document.body.firstChild.nextSibling.firstChild, 3);
|
||||
range.setEnd(document.body, 2);
|
||||
iter.initWithRange(Ci.nsIScriptableContentIterator.SUBTREE_ITERATOR, range);
|
||||
check(iter, [], "Initialized with range selecting '<b>def[</b>}<i>ghi'");
|
||||
iter.initWithPositions(Ci.nsIScriptableContentIterator.SUBTREE_ITERATOR,
|
||||
range.startContainer, range.startOffset,
|
||||
range.endContainer, range.endOffset);
|
||||
check(iter, [], "Initialized with positions selecting '<b>def[</b>}<i>ghi'");
|
||||
|
||||
range.setStart(document.body.firstChild.nextSibling, 1);
|
||||
range.setEnd(document.body, 2);
|
||||
iter.initWithRange(Ci.nsIScriptableContentIterator.SUBTREE_ITERATOR, range);
|
||||
check(iter, [], "Initialized with range selecting '<b>def{</b>}<i>ghi'");
|
||||
iter.initWithPositions(Ci.nsIScriptableContentIterator.SUBTREE_ITERATOR,
|
||||
range.startContainer, range.startOffset,
|
||||
range.endContainer, range.endOffset);
|
||||
check(iter, [], "Initialized with positions selecting '<b>def{</b>}<i>ghi'");
|
||||
|
||||
range.setStart(document.body.firstChild.nextSibling, 1);
|
||||
range.setEnd(document.body.firstChild.nextSibling.nextSibling, 0);
|
||||
iter.initWithRange(Ci.nsIScriptableContentIterator.SUBTREE_ITERATOR, range);
|
||||
check(iter, [], "Initialized with range selecting '<b>def{</b><i>}ghi'");
|
||||
iter.initWithPositions(Ci.nsIScriptableContentIterator.SUBTREE_ITERATOR,
|
||||
range.startContainer, range.startOffset,
|
||||
range.endContainer, range.endOffset);
|
||||
check(iter, [], "Initialized with positions selecting '<b>def{</b><i>}ghi'");
|
||||
|
||||
range.setStart(document.body.firstChild.nextSibling, 1);
|
||||
range.setEnd(document.body.firstChild.nextSibling.nextSibling.firstChild, 0);
|
||||
iter.initWithRange(Ci.nsIScriptableContentIterator.SUBTREE_ITERATOR, range);
|
||||
check(iter, [], "Initialized with range selecting '<b>def{</b><i>]ghi'");
|
||||
iter.initWithPositions(Ci.nsIScriptableContentIterator.SUBTREE_ITERATOR,
|
||||
range.startContainer, range.startOffset,
|
||||
range.endContainer, range.endOffset);
|
||||
check(iter, [], "Initialized with positions selecting '<b>def{</b><i>]ghi'");
|
||||
|
||||
range.setStart(document.body.firstChild.nextSibling.nextSibling, 0);
|
||||
range.setEnd(document.body, 3);
|
||||
iter.initWithRange(Ci.nsIScriptableContentIterator.SUBTREE_ITERATOR, range);
|
||||
check(iter,
|
||||
[document.body.firstChild.nextSibling.nextSibling.firstChild], // text in <i>
|
||||
"Initialized with range selecting '<i>{ghi</i>}jkl'");
|
||||
iter.initWithPositions(Ci.nsIScriptableContentIterator.SUBTREE_ITERATOR,
|
||||
range.startContainer, range.startOffset,
|
||||
range.endContainer, range.endOffset);
|
||||
check(iter,
|
||||
[document.body.firstChild.nextSibling.nextSibling.firstChild], // text in <i>
|
||||
"Initialized with positions selecting '<i>{ghi</i>}jkl'");
|
||||
|
||||
range.setStart(document.body.firstChild.nextSibling.nextSibling.firstChild, 3);
|
||||
range.setEnd(document.body, 3);
|
||||
iter.initWithRange(Ci.nsIScriptableContentIterator.SUBTREE_ITERATOR, range);
|
||||
check(iter, [], "Initialized with range selecting '<i>ghi[</i>}jkl'");
|
||||
iter.initWithPositions(Ci.nsIScriptableContentIterator.SUBTREE_ITERATOR,
|
||||
range.startContainer, range.startOffset,
|
||||
range.endContainer, range.endOffset);
|
||||
check(iter, [], "Initialized with positions selecting '<i>ghi[</i>}jkl'");
|
||||
|
||||
range.setStart(document.body.firstChild.nextSibling.nextSibling, 1);
|
||||
range.setEnd(document.body, 3);
|
||||
iter.initWithRange(Ci.nsIScriptableContentIterator.SUBTREE_ITERATOR, range);
|
||||
check(iter, [], "Initialized with range selecting '<i>ghi{</i>}jkl'");
|
||||
iter.initWithPositions(Ci.nsIScriptableContentIterator.SUBTREE_ITERATOR,
|
||||
range.startContainer, range.startOffset,
|
||||
range.endContainer, range.endOffset);
|
||||
check(iter, [], "Initialized with positions selecting '<i>ghi{</i>}jkl'");
|
||||
|
||||
range.setStart(document.body.firstChild.nextSibling.nextSibling, 1);
|
||||
range.setEnd(document.body.firstChild.nextSibling.nextSibling.nextSibling, 0);
|
||||
iter.initWithRange(Ci.nsIScriptableContentIterator.SUBTREE_ITERATOR, range);
|
||||
check(iter, [], "Initialized with range selecting '<i>ghi{</i>]jkl'");
|
||||
iter.initWithPositions(Ci.nsIScriptableContentIterator.SUBTREE_ITERATOR,
|
||||
range.startContainer, range.startOffset,
|
||||
range.endContainer, range.endOffset);
|
||||
check(iter, [], "Initialized with positions selecting '<i>ghi{</i>]jkl'");
|
||||
|
||||
/**
|
||||
* range/positions around <br> elements.
|
||||
*/
|
||||
document.body.innerHTML = "abc<br>def";
|
||||
range = document.createRange();
|
||||
range.setStart(document.body.firstChild, 3);
|
||||
range.setEnd(document.body.firstChild.nextSibling.nextSibling, 0);
|
||||
iter.initWithRange(Ci.nsIScriptableContentIterator.SUBTREE_ITERATOR, range);
|
||||
check(iter,
|
||||
[document.body.firstChild.nextSibling], // <br>
|
||||
"Initialized with range selecting 'abc[<br>]def'");
|
||||
iter.initWithPositions(Ci.nsIScriptableContentIterator.SUBTREE_ITERATOR,
|
||||
range.startContainer, range.startOffset,
|
||||
range.endContainer, range.endOffset);
|
||||
check(iter,
|
||||
[document.body.firstChild.nextSibling], // <br>
|
||||
"Initialized with positions selecting 'abc[<br>]def'");
|
||||
|
||||
range.setStart(document.body, 1);
|
||||
range.setEnd(document.body.firstChild.nextSibling.nextSibling, 0);
|
||||
iter.initWithRange(Ci.nsIScriptableContentIterator.SUBTREE_ITERATOR, range);
|
||||
check(iter,
|
||||
[document.body.firstChild.nextSibling], // <br>
|
||||
"Initialized with range selecting 'abc{<br>]def'");
|
||||
iter.initWithPositions(Ci.nsIScriptableContentIterator.SUBTREE_ITERATOR,
|
||||
range.startContainer, range.startOffset,
|
||||
range.endContainer, range.endOffset);
|
||||
check(iter,
|
||||
[document.body.firstChild.nextSibling], // <br>
|
||||
"Initialized with positions selecting 'abc{<br>]def'");
|
||||
|
||||
range.setStart(document.body.firstChild.nextSibling, 0);
|
||||
range.setEnd(document.body.firstChild.nextSibling.nextSibling, 0);
|
||||
iter.initWithRange(Ci.nsIScriptableContentIterator.SUBTREE_ITERATOR, range);
|
||||
check(iter, [], "Initialized with range selecting 'abc{<br>]def' (starting in <br>)");
|
||||
iter.initWithPositions(Ci.nsIScriptableContentIterator.SUBTREE_ITERATOR,
|
||||
range.startContainer, range.startOffset,
|
||||
range.endContainer, range.endOffset);
|
||||
check(iter, [], "Initialized with positions selecting 'abc{<br>]def' (starting in <br>)");
|
||||
|
||||
range.setStart(document.body, 1);
|
||||
range.setEnd(document.body, 2);
|
||||
iter.initWithRange(Ci.nsIScriptableContentIterator.SUBTREE_ITERATOR, range);
|
||||
check(iter,
|
||||
[document.body.firstChild.nextSibling], // <br>
|
||||
"Initialized with range selecting 'abc{<br>}def'");
|
||||
iter.initWithPositions(Ci.nsIScriptableContentIterator.SUBTREE_ITERATOR,
|
||||
range.startContainer, range.startOffset,
|
||||
range.endContainer, range.endOffset);
|
||||
check(iter,
|
||||
[document.body.firstChild.nextSibling], // <br>
|
||||
"Initialized with positions selecting 'abc{<br>}def'");
|
||||
|
||||
range.setStart(document.body.firstChild, 3);
|
||||
range.setEnd(document.body.firstChild.nextSibling, 0);
|
||||
iter.initWithRange(Ci.nsIScriptableContentIterator.SUBTREE_ITERATOR, range);
|
||||
check(iter, [], "Initialized with range selecting 'abc[}<br>def' (ending in <br>)");
|
||||
iter.initWithPositions(Ci.nsIScriptableContentIterator.SUBTREE_ITERATOR,
|
||||
range.startContainer, range.startOffset,
|
||||
range.endContainer, range.endOffset);
|
||||
check(iter, [], "Initialized with positions selecting 'abc[}<br>def' (ending in <br>)");
|
||||
|
||||
finish();
|
||||
});
|
||||
</script>
|
||||
</head>
|
||||
<body></body>
|
||||
</html>
|
|
@ -27,6 +27,16 @@ InputEvent::InputEvent(EventTarget* aOwner, nsPresContext* aPresContext,
|
|||
}
|
||||
}
|
||||
|
||||
void InputEvent::GetInputType(nsAString& aInputType) {
|
||||
InternalEditorInputEvent* editorInputEvent = mEvent->AsEditorInputEvent();
|
||||
MOZ_ASSERT(editorInputEvent);
|
||||
if (editorInputEvent->mInputType == EditorInputType::eUnknown) {
|
||||
aInputType = mInputTypeValue;
|
||||
} else {
|
||||
editorInputEvent->GetDOMInputTypeName(aInputType);
|
||||
}
|
||||
}
|
||||
|
||||
bool InputEvent::IsComposing() {
|
||||
return mEvent->AsEditorInputEvent()->mIsComposing;
|
||||
}
|
||||
|
@ -40,6 +50,11 @@ already_AddRefed<InputEvent> InputEvent::Constructor(
|
|||
e->InitUIEvent(aType, aParam.mBubbles, aParam.mCancelable, aParam.mView,
|
||||
aParam.mDetail);
|
||||
InternalEditorInputEvent* internalEvent = e->mEvent->AsEditorInputEvent();
|
||||
internalEvent->mInputType =
|
||||
InternalEditorInputEvent::GetEditorInputType(aParam.mInputType);
|
||||
if (internalEvent->mInputType == EditorInputType::eUnknown) {
|
||||
e->mInputTypeValue = aParam.mInputType;
|
||||
}
|
||||
internalEvent->mIsComposing = aParam.mIsComposing;
|
||||
e->SetTrusted(trusted);
|
||||
e->SetComposed(aParam.mComposed);
|
||||
|
|
|
@ -31,10 +31,15 @@ class InputEvent : public UIEvent {
|
|||
return InputEvent_Binding::Wrap(aCx, this, aGivenProto);
|
||||
}
|
||||
|
||||
void GetInputType(nsAString& aInputType);
|
||||
bool IsComposing();
|
||||
|
||||
protected:
|
||||
~InputEvent() {}
|
||||
|
||||
// mInputTypeValue stores inputType attribute value if the instance is
|
||||
// created by script and not initialized with known inputType value.
|
||||
nsString mInputTypeValue;
|
||||
};
|
||||
|
||||
} // namespace dom
|
||||
|
|
|
@ -0,0 +1,72 @@
|
|||
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
||||
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
||||
* You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
/**
|
||||
* This header file defines all inputType values which are used for DOM
|
||||
* InputEvent.inputType.
|
||||
* You must define NS_DEFINE_INPUTTYPE macro before including this.
|
||||
*
|
||||
* It must have two arguments, (aCPPName, aDOMName)
|
||||
* aCPPName is usable name for a part of C++ constants.
|
||||
* aDOMName is the actual value declared by the specs:
|
||||
* Level 1:
|
||||
* https://rawgit.com/w3c/input-events/v1/index.html#interface-InputEvent-Attributes
|
||||
* Level 2:
|
||||
* https://w3c.github.io/input-events/index.html#interface-InputEvent-Attributes
|
||||
*/
|
||||
|
||||
NS_DEFINE_INPUTTYPE(InsertText, "insertText")
|
||||
NS_DEFINE_INPUTTYPE(InsertReplacementText, "insertReplacementText")
|
||||
NS_DEFINE_INPUTTYPE(InsertLineBreak, "insertLineBreak")
|
||||
NS_DEFINE_INPUTTYPE(InsertParagraph, "insertParagraph")
|
||||
NS_DEFINE_INPUTTYPE(InsertOrderedList, "insertOrderedList")
|
||||
NS_DEFINE_INPUTTYPE(InsertUnorderedList, "insertUnorderedList")
|
||||
NS_DEFINE_INPUTTYPE(InsertHorizontalRule, "insertHorizontalRule")
|
||||
NS_DEFINE_INPUTTYPE(InsertFromYank, "insertFromYank")
|
||||
NS_DEFINE_INPUTTYPE(InsertFromDrop, "insertFromDrop")
|
||||
NS_DEFINE_INPUTTYPE(InsertFromPaste, "insertFromPaste")
|
||||
NS_DEFINE_INPUTTYPE(InsertTranspose, "insertTranspose")
|
||||
NS_DEFINE_INPUTTYPE(InsertCompositionText, "insertCompositionText")
|
||||
NS_DEFINE_INPUTTYPE(InsertFromComposition,
|
||||
"insertFromComposition") // Level 2
|
||||
NS_DEFINE_INPUTTYPE(InsertLink, "insertLink")
|
||||
NS_DEFINE_INPUTTYPE(DeleteByComposition,
|
||||
"deleteByComposition") // Level 2
|
||||
NS_DEFINE_INPUTTYPE(DeleteCompositionText,
|
||||
"deleteCompositionText") // Level 2
|
||||
NS_DEFINE_INPUTTYPE(DeleteWordBackward, "deleteWordBackward")
|
||||
NS_DEFINE_INPUTTYPE(DeleteWordForward, "deleteWordForward")
|
||||
NS_DEFINE_INPUTTYPE(DeleteSoftLineBackward, "deleteSoftLineBackward")
|
||||
NS_DEFINE_INPUTTYPE(DeleteSoftLineForward, "deleteSoftLineForward")
|
||||
NS_DEFINE_INPUTTYPE(DeleteEntireSoftLine, "deleteEntireSoftLine")
|
||||
NS_DEFINE_INPUTTYPE(DeleteHardLineBackward, "deleteHardLineBackward")
|
||||
NS_DEFINE_INPUTTYPE(DeleteHardLineForward, "deleteHardLineForward")
|
||||
NS_DEFINE_INPUTTYPE(DeleteByDrag, "deleteByDrag")
|
||||
NS_DEFINE_INPUTTYPE(DeleteByCut, "deleteByCut")
|
||||
NS_DEFINE_INPUTTYPE(DeleteContent, "deleteContent")
|
||||
NS_DEFINE_INPUTTYPE(DeleteContentBackward, "deleteContentBackward")
|
||||
NS_DEFINE_INPUTTYPE(DeleteContentForward, "deleteContentForward")
|
||||
NS_DEFINE_INPUTTYPE(HistoryUndo, "historyUndo")
|
||||
NS_DEFINE_INPUTTYPE(HistoryRedo, "historyRedo")
|
||||
NS_DEFINE_INPUTTYPE(FormatBold, "formatBold")
|
||||
NS_DEFINE_INPUTTYPE(FormatItalic, "formatItalic")
|
||||
NS_DEFINE_INPUTTYPE(FormatUnderline, "formatUnderline")
|
||||
NS_DEFINE_INPUTTYPE(FormatStrikeThrough, "formatStrikeThrough")
|
||||
NS_DEFINE_INPUTTYPE(FormatSuperscript, "formatSuperscript")
|
||||
NS_DEFINE_INPUTTYPE(FormatSubscript, "formatSubscript")
|
||||
NS_DEFINE_INPUTTYPE(FormatJustifyFull, "formatJustifyFull")
|
||||
NS_DEFINE_INPUTTYPE(FormatJustifyCenter, "formatJustifyCenter")
|
||||
NS_DEFINE_INPUTTYPE(FormatJustifyRight, "formatJustifyRight")
|
||||
NS_DEFINE_INPUTTYPE(FormatJustifyLeft, "formatJustifyLeft")
|
||||
NS_DEFINE_INPUTTYPE(FormatIndent, "formatIndent")
|
||||
NS_DEFINE_INPUTTYPE(FormatOutdent, "formatOutdent")
|
||||
NS_DEFINE_INPUTTYPE(FormatRemove, "formatRemove")
|
||||
NS_DEFINE_INPUTTYPE(FormatSetBlockTextDirection, "formatSetBlockTextDirection")
|
||||
NS_DEFINE_INPUTTYPE(FormatSetInlineTextDirection,
|
||||
"formatSetInlineTextDirection")
|
||||
NS_DEFINE_INPUTTYPE(FormatBackColor, "formatBackColor")
|
||||
NS_DEFINE_INPUTTYPE(FormatFontColor, "formatFontColor")
|
||||
NS_DEFINE_INPUTTYPE(FormatFontName, "formatFontName")
|
|
@ -35,6 +35,7 @@ EXPORTS.mozilla += [
|
|||
'EventStates.h',
|
||||
'IMEContentObserver.h',
|
||||
'IMEStateManager.h',
|
||||
'InputTypeList.h',
|
||||
'InternalMutationEvent.h',
|
||||
'JSEventHandler.h',
|
||||
'KeyNameList.h',
|
||||
|
|
|
@ -885,6 +885,25 @@ is(e.type, "hello", "Wrong event type!");
|
|||
is(e.isTrusted, false, "Event shouldn't be trusted!");
|
||||
is(e.eventPhase, Event.NONE, "Wrong event phase");
|
||||
|
||||
// InputEvent
|
||||
e = new InputEvent("hello", {data: "something data", inputType: "invalid input type", isComposing: true});
|
||||
is(e.type, "hello", "InputEvent should set type attribute");
|
||||
todo_is(e.data, "something data", "InputEvent should have data attribute");
|
||||
is(e.inputType, "invalid input type", "InputEvent should have inputType attribute");
|
||||
is(e.isComposing, true, "InputEvent should have isComposing attribute");
|
||||
|
||||
e = new InputEvent("hello", {inputType: "insertText"});
|
||||
is(e.inputType, "insertText", "InputEvent.inputType should return valid inputType from EditorInputType enum");
|
||||
e = new InputEvent("hello", {inputType: "deleteWordBackward"});
|
||||
is(e.inputType, "deleteWordBackward", "InputEvent.inputType should return valid inputType from EditorInputType enum");
|
||||
e = new InputEvent("hello", {inputType: "formatFontName"});
|
||||
is(e.inputType, "formatFontName", "InputEvent.inputType should return valid inputType from EditorInputType enum");
|
||||
|
||||
e = new InputEvent("input", {});
|
||||
todo_is(e.data, "", "InputEvent.data should be empty string in default");
|
||||
is(e.inputType, "", "InputEvent.inputType should be empty string in default");
|
||||
is(e.isComposing, false, "InputEvent.isComposing should be false in default");
|
||||
|
||||
</script>
|
||||
</pre>
|
||||
</body>
|
||||
|
|
|
@ -2426,13 +2426,15 @@ bool nsTextEditorState::SetValue(const nsAString& aValue,
|
|||
}
|
||||
|
||||
// If this is called as part of user input, we need to dispatch "input"
|
||||
// event since web apps may want to know the user operation.
|
||||
// event with "insertReplacementText" since web apps may want to know
|
||||
// the user operation which changes editor value with a built-in function
|
||||
// like autocomplete, password manager, session restore, etc.
|
||||
if (aFlags & eSetValue_BySetUserInput) {
|
||||
nsCOMPtr<Element> element = do_QueryInterface(textControlElement);
|
||||
MOZ_ASSERT(element);
|
||||
RefPtr<TextEditor> textEditor;
|
||||
DebugOnly<nsresult> rvIgnored =
|
||||
nsContentUtils::DispatchInputEvent(element, textEditor);
|
||||
RefPtr<TextEditor> textEditor; // See bug 1506439
|
||||
DebugOnly<nsresult> rvIgnored = nsContentUtils::DispatchInputEvent(
|
||||
element, EditorInputType::eInsertReplacementText, textEditor);
|
||||
NS_WARNING_ASSERTION(NS_SUCCEEDED(rvIgnored),
|
||||
"Failed to dispatch input event");
|
||||
}
|
||||
|
|
|
@ -162,6 +162,8 @@ SimpleTest.waitForFocus(() => {
|
|||
} else {
|
||||
ok(inputEvents[0] instanceof InputEvent,
|
||||
`"input" event should be dispatched with InputEvent interface when setUserInput("${test.input.before}") is called before ${tag} gets focus`);
|
||||
is(inputEvents[0].inputType, "insertReplacementText",
|
||||
`inputType should be "insertReplacementText" when setUserInput("${test.input.before}") is called before ${tag} gets focus`);
|
||||
}
|
||||
} else {
|
||||
ok(inputEvents[0] instanceof Event && !(inputEvents[0] instanceof UIEvent),
|
||||
|
@ -218,6 +220,8 @@ SimpleTest.waitForFocus(() => {
|
|||
} else {
|
||||
ok(inputEvents[0] instanceof InputEvent,
|
||||
`"input" event should be dispatched with InputEvent interface when setUserInput("${test.input.after}") is called after ${tag} gets focus`);
|
||||
is(inputEvents[0].inputType, "insertReplacementText",
|
||||
`inputType should be "insertReplacementText" when setUserInput("${test.input.after}") is called after ${tag} gets focus`);
|
||||
}
|
||||
} else {
|
||||
ok(inputEvents[0] instanceof Event && !(inputEvents[0] instanceof UIEvent),
|
||||
|
|
|
@ -40,6 +40,7 @@ https://bugzilla.mozilla.org/show_bug.cgi?id=851780
|
|||
|
||||
const isDesktop = !/Mobile|Tablet/.test(navigator.userAgent);
|
||||
|
||||
let expectedInputType = "";
|
||||
function checkIfInputIsInputEvent(aEvent, aToDo, aDescription) {
|
||||
if (aToDo) {
|
||||
// Probably, key operation should fire "input" event with InputEvent interface.
|
||||
|
@ -49,6 +50,8 @@ https://bugzilla.mozilla.org/show_bug.cgi?id=851780
|
|||
} else {
|
||||
ok(aEvent instanceof InputEvent,
|
||||
`"input" event should be dispatched with InputEvent interface ${aDescription}`);
|
||||
is(aEvent.inputType, expectedInputType,
|
||||
`inputType should be "${expectedInputType}" ${aDescription}`);
|
||||
}
|
||||
is(aEvent.cancelable, false,
|
||||
`"input" event should be never cancelable ${aDescription}`);
|
||||
|
@ -128,14 +131,18 @@ https://bugzilla.mozilla.org/show_bug.cgi?id=851780
|
|||
for (var i = 0; i < textTypes.length; ++i) {
|
||||
input = document.getElementById("input_" + textTypes[i]);
|
||||
input.focus();
|
||||
expectedInputType = "insertLineBreak";
|
||||
synthesizeKey("KEY_Enter");
|
||||
is(textInput[i], 0, "input event shouldn't be dispatched on " + textTypes[i] + " input element");
|
||||
|
||||
expectedInputType = "insertText";
|
||||
sendString("m");
|
||||
is(textInput[i], 1, textTypes[i] + " input element should have dispatched input event.");
|
||||
expectedInputType = "insertLineBreak";
|
||||
synthesizeKey("KEY_Enter");
|
||||
is(textInput[i], 1, "input event shouldn't be dispatched on " + textTypes[i] + " input element");
|
||||
|
||||
expectedInputType = "deleteContentBackward";
|
||||
synthesizeKey("KEY_Backspace");
|
||||
is(textInput[i], 2, textTypes[i] + " input element should have dispatched input event.");
|
||||
}
|
||||
|
@ -143,18 +150,21 @@ https://bugzilla.mozilla.org/show_bug.cgi?id=851780
|
|||
// Some scenarios of value changing from script and from user input.
|
||||
input = document.getElementById("input_text");
|
||||
input.focus();
|
||||
expectedInputType = "insertText";
|
||||
sendString("f");
|
||||
is(textInput[0], 3, "input event should have been dispatched");
|
||||
input.blur();
|
||||
is(textInput[0], 3, "input event should not have been dispatched");
|
||||
|
||||
input.focus();
|
||||
expectedInputType = "insertText";
|
||||
input.value = 'foo';
|
||||
is(textInput[0], 3, "input event should not have been dispatched");
|
||||
input.blur();
|
||||
is(textInput[0], 3, "input event should not have been dispatched");
|
||||
|
||||
input.focus();
|
||||
expectedInputType = "insertText";
|
||||
sendString("f");
|
||||
is(textInput[0], 4, "input event should have been dispatched");
|
||||
input.value = 'bar';
|
||||
|
@ -165,6 +175,7 @@ https://bugzilla.mozilla.org/show_bug.cgi?id=851780
|
|||
// Same for textarea.
|
||||
var textarea = document.getElementById("textarea");
|
||||
textarea.focus();
|
||||
expectedInputType = "insertText";
|
||||
sendString("f");
|
||||
is(textareaInput, 1, "input event should have been dispatched");
|
||||
textarea.blur();
|
||||
|
@ -177,10 +188,12 @@ https://bugzilla.mozilla.org/show_bug.cgi?id=851780
|
|||
is(textareaInput, 1, "input event should not have been dispatched");
|
||||
|
||||
textarea.focus();
|
||||
expectedInputType = "insertText";
|
||||
sendString("f");
|
||||
is(textareaInput, 2, "input event should have been dispatched");
|
||||
textarea.value = 'bar';
|
||||
is(textareaInput, 2, "input event should not have been dispatched");
|
||||
expectedInputType = "deleteContentBackward";
|
||||
synthesizeKey("KEY_Backspace");
|
||||
is(textareaInput, 3, "input event should have been dispatched");
|
||||
textarea.blur();
|
||||
|
@ -261,6 +274,9 @@ https://bugzilla.mozilla.org/show_bug.cgi?id=851780
|
|||
if (isDesktop) { // up/down arrow keys not supported on android/b2g
|
||||
number.value = "";
|
||||
number.focus();
|
||||
// <input type="number">'s inputType value hasn't been decided, see
|
||||
// https://github.com/w3c/input-events/issues/88
|
||||
expectedInputType = "";
|
||||
synthesizeKey("KEY_ArrowUp");
|
||||
is(numberInput, 1, "input event should be dispatched for up/down arrow key keypress");
|
||||
is(number.value, "1", "sanity check value of number control after keypress");
|
||||
|
|
|
@ -570,17 +570,15 @@ nsresult MediaEngineWebRTCMicrophoneSource::Start(
|
|||
mTrackID, mPrincipal);
|
||||
|
||||
RefPtr<MediaEngineWebRTCMicrophoneSource> that = this;
|
||||
RefPtr<MediaStreamGraphImpl> gripGraph = mStream->GraphImpl();
|
||||
NS_DispatchToMainThread(
|
||||
media::NewRunnableFrom([that, graph = std::move(gripGraph), deviceID,
|
||||
stream = mStream, track = mTrackID]() {
|
||||
if (graph) {
|
||||
NS_DispatchToMainThread(media::NewRunnableFrom(
|
||||
[that, deviceID, stream = mStream, track = mTrackID]() {
|
||||
if (MediaStreamGraphImpl* graph = stream->GraphImpl()) {
|
||||
graph->AppendMessage(MakeUnique<StartStopMessage>(
|
||||
that->mInputProcessing, StartStopMessage::Start));
|
||||
stream->SetPullingEnabled(track, true);
|
||||
}
|
||||
|
||||
stream->OpenAudioInput(deviceID, that->mInputProcessing);
|
||||
stream->SetPullingEnabled(track, true);
|
||||
|
||||
return NS_OK;
|
||||
}));
|
||||
|
@ -606,11 +604,10 @@ nsresult MediaEngineWebRTCMicrophoneSource::Stop(
|
|||
}
|
||||
|
||||
RefPtr<MediaEngineWebRTCMicrophoneSource> that = this;
|
||||
RefPtr<MediaStreamGraphImpl> gripGraph = mStream->GraphImpl();
|
||||
NS_DispatchToMainThread(
|
||||
media::NewRunnableFrom([that, graph = std::move(gripGraph),
|
||||
stream = mStream, track = mTrackID]() {
|
||||
if (graph) {
|
||||
media::NewRunnableFrom([that, stream = mStream, track = mTrackID]() {
|
||||
if (MediaStreamGraphImpl* graph = stream->GraphImpl()) {
|
||||
stream->SetPullingEnabled(track, false);
|
||||
graph->AppendMessage(MakeUnique<StartStopMessage>(
|
||||
that->mInputProcessing, StartStopMessage::Stop));
|
||||
}
|
||||
|
@ -618,7 +615,6 @@ nsresult MediaEngineWebRTCMicrophoneSource::Stop(
|
|||
CubebUtils::AudioDeviceID deviceID = that->mDeviceInfo->DeviceID();
|
||||
Maybe<CubebUtils::AudioDeviceID> id = Some(deviceID);
|
||||
stream->CloseAudioInput(id, that->mInputProcessing);
|
||||
stream->SetPullingEnabled(track, false);
|
||||
|
||||
return NS_OK;
|
||||
}));
|
||||
|
|
|
@ -193,14 +193,18 @@ add_task(async function test_input_oncopy() {
|
|||
contentInput.setSelectionRange(2, 8);
|
||||
|
||||
var oncopy_fired = false;
|
||||
var oninput_fired = false;
|
||||
contentInput.oncopy = function() { oncopy_fired = true; };
|
||||
contentInput.oninput = function () { oninput_fired = true; };
|
||||
try {
|
||||
await putOnClipboard("PUT TE", () => {
|
||||
synthesizeKey("c", {accelKey: 1});
|
||||
}, "copy on plaintext editor set clipboard correctly");
|
||||
ok(oncopy_fired, "copy event firing on plaintext editor");
|
||||
ok(!oninput_fired, "input event shouldn't be fired on plaintext editor by copy");
|
||||
} finally {
|
||||
contentInput.oncopy = null;
|
||||
contentInput.oninput = null;
|
||||
}
|
||||
});
|
||||
|
||||
|
@ -212,16 +216,25 @@ add_task(async function test_input_oncut() {
|
|||
// that the input itself is empty.
|
||||
selectContentInput();
|
||||
var oncut_fired = false;
|
||||
var oninput_count = 0;
|
||||
var inputType = "";
|
||||
contentInput.oncut = function() { oncut_fired = true; };
|
||||
contentInput.oninput = function (aEvent) {
|
||||
oninput_count++;
|
||||
inputType = aEvent.inputType;
|
||||
};
|
||||
try {
|
||||
await putOnClipboard("INPUT TEXT", () => {
|
||||
synthesizeKey("x", {accelKey: 1});
|
||||
}, "cut on plaintext editor set clipboard correctly");
|
||||
ok(oncut_fired, "cut event firing on plaintext editor");
|
||||
is(oninput_count, 1, "input event should be fired once by cut");
|
||||
is(inputType, "deleteByCut", "inputType of the input event should be \"deleteByCut\"");
|
||||
is(contentInput.value, "",
|
||||
"cut on plaintext editor emptied editor");
|
||||
} finally {
|
||||
contentInput.oncut = null;
|
||||
contentInput.oninput = null;
|
||||
}
|
||||
});
|
||||
|
||||
|
@ -233,16 +246,26 @@ add_task(async function test_input_onpaste() {
|
|||
// input value did change (ie. paste succeeded).
|
||||
selectContentInput();
|
||||
var onpaste_fired = false;
|
||||
var oninput_count = 0;
|
||||
var inputType = "";
|
||||
contentInput.onpaste = function() { onpaste_fired = true; };
|
||||
contentInput.oninput = function(aEvent) {
|
||||
oninput_count++;
|
||||
inputType = aEvent.inputType;
|
||||
};
|
||||
|
||||
try {
|
||||
synthesizeKey("v", {accelKey: 1});
|
||||
ok(onpaste_fired, "paste event firing on plaintext editor");
|
||||
is(getClipboardText(), clipboardInitialValue,
|
||||
"paste on plaintext editor did not modify clipboard contents");
|
||||
is(oninput_count, 1, "input event should be fired once by cut");
|
||||
is(inputType, "insertFromPaste", "inputType of the input event should be \"insertFromPaste\"");
|
||||
is(contentInput.value, clipboardInitialValue,
|
||||
"paste on plaintext editor did modify editor value");
|
||||
} finally {
|
||||
contentInput.onpaste = null;
|
||||
contentInput.oninput = null;
|
||||
}
|
||||
});
|
||||
|
||||
|
@ -254,6 +277,9 @@ add_task(async function test_input_oncopy_abort() {
|
|||
selectContentInput();
|
||||
var oncopy_fired = false;
|
||||
contentInput.oncopy = function() { oncopy_fired = true; return false; };
|
||||
contentInput.oninput = function() {
|
||||
ok(false, "input event shouldn't be fired by copy but canceled");
|
||||
};
|
||||
try {
|
||||
await wontPutOnClipboard(clipboardInitialValue, () => {
|
||||
synthesizeKey("c", {accelKey: 1});
|
||||
|
@ -261,6 +287,7 @@ add_task(async function test_input_oncopy_abort() {
|
|||
ok(oncopy_fired, "copy event (to-be-cancelled) firing on plaintext editor");
|
||||
} finally {
|
||||
contentInput.oncopy = null;
|
||||
contentInput.oninput = null;
|
||||
}
|
||||
});
|
||||
|
||||
|
@ -273,6 +300,9 @@ add_task(async function test_input_oncut_abort() {
|
|||
selectContentInput();
|
||||
var oncut_fired = false;
|
||||
contentInput.oncut = function() { oncut_fired = true; return false; };
|
||||
contentInput.oninput = function() {
|
||||
ok(false, "input event shouldn't be fired by cut but canceled");
|
||||
};
|
||||
try {
|
||||
await wontPutOnClipboard(clipboardInitialValue, () => {
|
||||
synthesizeKey("x", {accelKey: 1});
|
||||
|
@ -282,6 +312,7 @@ add_task(async function test_input_oncut_abort() {
|
|||
"aborted cut on plaintext editor did not modify editor contents");
|
||||
} finally {
|
||||
contentInput.oncut = null;
|
||||
contentInput.oninput = null;
|
||||
}
|
||||
});
|
||||
|
||||
|
@ -294,6 +325,9 @@ add_task(async function test_input_onpaste_abort() {
|
|||
selectContentInput();
|
||||
var onpaste_fired = false;
|
||||
contentInput.onpaste = function() { onpaste_fired = true; return false; };
|
||||
contentInput.oninput = function() {
|
||||
ok(false, "input event shouldn't be fired by paste but canceled");
|
||||
};
|
||||
try {
|
||||
synthesizeKey("v", {accelKey: 1});
|
||||
ok(onpaste_fired,
|
||||
|
@ -304,6 +338,7 @@ add_task(async function test_input_onpaste_abort() {
|
|||
"aborted paste on plaintext editor did not modify modified editor value");
|
||||
} finally {
|
||||
contentInput.onpaste = null;
|
||||
contentInput.oninput = null;
|
||||
}
|
||||
});
|
||||
|
||||
|
|
|
@ -8,9 +8,13 @@
|
|||
interface InputEvent : UIEvent
|
||||
{
|
||||
readonly attribute boolean isComposing;
|
||||
|
||||
[Pref="dom.inputevent.inputtype.enabled"]
|
||||
readonly attribute DOMString inputType;
|
||||
};
|
||||
|
||||
dictionary InputEventInit : UIEventInit
|
||||
{
|
||||
boolean isComposing = false;
|
||||
DOMString inputType = "";
|
||||
};
|
||||
|
|
|
@ -6,6 +6,9 @@
|
|||
#ifndef mozilla_EditAction_h
|
||||
#define mozilla_EditAction_h
|
||||
|
||||
#include "mozilla/EventForwards.h"
|
||||
#include "mozilla/StaticPrefs.h"
|
||||
|
||||
namespace mozilla {
|
||||
|
||||
/**
|
||||
|
@ -64,6 +67,10 @@ enum class EditAction {
|
|||
// This may be set even when Selection is not collapsed.
|
||||
eDeleteToEndOfSoftLine,
|
||||
|
||||
// eDeleteByDrag indicates to remove selection by dragging the content
|
||||
// to different place.
|
||||
eDeleteByDrag,
|
||||
|
||||
// eStartComposition indicates that user starts composition.
|
||||
eStartComposition,
|
||||
|
||||
|
@ -463,6 +470,124 @@ enum class EditSubAction : int32_t {
|
|||
eCreateBogusNode,
|
||||
};
|
||||
|
||||
inline EditorInputType ToInputType(EditAction aEditAction) {
|
||||
switch (aEditAction) {
|
||||
case EditAction::eInsertText:
|
||||
return EditorInputType::eInsertText;
|
||||
case EditAction::eReplaceText:
|
||||
return EditorInputType::eInsertReplacementText;
|
||||
case EditAction::eInsertLineBreak:
|
||||
return EditorInputType::eInsertLineBreak;
|
||||
case EditAction::eInsertParagraphSeparator:
|
||||
return EditorInputType::eInsertParagraph;
|
||||
case EditAction::eInsertOrderedListElement:
|
||||
case EditAction::eRemoveOrderedListElement:
|
||||
return EditorInputType::eInsertOrderedList;
|
||||
case EditAction::eInsertUnorderedListElement:
|
||||
case EditAction::eRemoveUnorderedListElement:
|
||||
return EditorInputType::eInsertUnorderedList;
|
||||
case EditAction::eInsertHorizontalRuleElement:
|
||||
return EditorInputType::eInsertHorizontalRule;
|
||||
case EditAction::eDrop:
|
||||
return EditorInputType::eInsertFromDrop;
|
||||
case EditAction::ePaste:
|
||||
return EditorInputType::eInsertFromPaste;
|
||||
case EditAction::eUpdateComposition:
|
||||
return EditorInputType::eInsertCompositionText;
|
||||
case EditAction::eCommitComposition:
|
||||
if (StaticPrefs::dom_input_events_conform_to_level_1()) {
|
||||
return EditorInputType::eInsertCompositionText;
|
||||
}
|
||||
return EditorInputType::eInsertFromComposition;
|
||||
case EditAction::eCancelComposition:
|
||||
if (StaticPrefs::dom_input_events_conform_to_level_1()) {
|
||||
return EditorInputType::eInsertCompositionText;
|
||||
}
|
||||
return EditorInputType::eDeleteCompositionText;
|
||||
case EditAction::eDeleteByComposition:
|
||||
if (StaticPrefs::dom_input_events_conform_to_level_1()) {
|
||||
// XXX Or EditorInputType::eDeleteContent? I don't know which IME may
|
||||
// causes this situation.
|
||||
return EditorInputType::eInsertCompositionText;
|
||||
}
|
||||
return EditorInputType::eDeleteByComposition;
|
||||
case EditAction::eInsertLinkElement:
|
||||
return EditorInputType::eInsertLink;
|
||||
case EditAction::eDeleteWordBackward:
|
||||
return EditorInputType::eDeleteWordBackward;
|
||||
case EditAction::eDeleteWordForward:
|
||||
return EditorInputType::eDeleteWordForward;
|
||||
case EditAction::eDeleteToBeginningOfSoftLine:
|
||||
return EditorInputType::eDeleteSoftLineBackward;
|
||||
case EditAction::eDeleteToEndOfSoftLine:
|
||||
return EditorInputType::eDeleteSoftLineForward;
|
||||
case EditAction::eDeleteByDrag:
|
||||
return EditorInputType::eDeleteByDrag;
|
||||
case EditAction::eCut:
|
||||
return EditorInputType::eDeleteByCut;
|
||||
case EditAction::eDeleteSelection:
|
||||
case EditAction::eRemoveTableRowElement:
|
||||
case EditAction::eRemoveTableColumn:
|
||||
case EditAction::eRemoveTableElement:
|
||||
case EditAction::eDeleteTableCellContents:
|
||||
case EditAction::eRemoveTableCellElement:
|
||||
return EditorInputType::eDeleteContent;
|
||||
case EditAction::eDeleteBackward:
|
||||
return EditorInputType::eDeleteContentBackward;
|
||||
case EditAction::eDeleteForward:
|
||||
return EditorInputType::eDeleteContentForward;
|
||||
case EditAction::eUndo:
|
||||
return EditorInputType::eHistoryUndo;
|
||||
case EditAction::eRedo:
|
||||
return EditorInputType::eHistoryRedo;
|
||||
case EditAction::eSetFontWeightProperty:
|
||||
case EditAction::eRemoveFontWeightProperty:
|
||||
return EditorInputType::eFormatBold;
|
||||
case EditAction::eSetTextStyleProperty:
|
||||
case EditAction::eRemoveTextStyleProperty:
|
||||
return EditorInputType::eFormatItalic;
|
||||
case EditAction::eSetTextDecorationPropertyUnderline:
|
||||
case EditAction::eRemoveTextDecorationPropertyUnderline:
|
||||
return EditorInputType::eFormatUnderline;
|
||||
case EditAction::eSetTextDecorationPropertyLineThrough:
|
||||
case EditAction::eRemoveTextDecorationPropertyLineThrough:
|
||||
return EditorInputType::eFormatStrikeThrough;
|
||||
case EditAction::eSetVerticalAlignPropertySuper:
|
||||
case EditAction::eRemoveVerticalAlignPropertySuper:
|
||||
return EditorInputType::eFormatSuperscript;
|
||||
case EditAction::eSetVerticalAlignPropertySub:
|
||||
case EditAction::eRemoveVerticalAlignPropertySub:
|
||||
return EditorInputType::eFormatSubscript;
|
||||
case EditAction::eJustify:
|
||||
return EditorInputType::eFormatJustifyFull;
|
||||
case EditAction::eAlignCenter:
|
||||
return EditorInputType::eFormatJustifyCenter;
|
||||
case EditAction::eAlignRight:
|
||||
return EditorInputType::eFormatJustifyRight;
|
||||
case EditAction::eAlignLeft:
|
||||
return EditorInputType::eFormatJustifyLeft;
|
||||
case EditAction::eIndent:
|
||||
return EditorInputType::eFormatIndent;
|
||||
case EditAction::eOutdent:
|
||||
return EditorInputType::eFormatOutdent;
|
||||
case EditAction::eRemoveAllInlineStyleProperties:
|
||||
return EditorInputType::eFormatRemove;
|
||||
case EditAction::eSetTextDirection:
|
||||
return EditorInputType::eFormatSetBlockTextDirection;
|
||||
case EditAction::eSetBackgroundColorPropertyInline:
|
||||
case EditAction::eRemoveBackgroundColorPropertyInline:
|
||||
return EditorInputType::eFormatBackColor;
|
||||
case EditAction::eSetColorProperty:
|
||||
case EditAction::eRemoveColorProperty:
|
||||
return EditorInputType::eFormatFontColor;
|
||||
case EditAction::eSetFontFamilyProperty:
|
||||
case EditAction::eRemoveFontFamilyProperty:
|
||||
return EditorInputType::eFormatFontName;
|
||||
default:
|
||||
return EditorInputType::eUnknown;
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace mozilla
|
||||
|
||||
inline bool operator!(const mozilla::EditSubAction& aEditSubAction) {
|
||||
|
|
|
@ -2017,14 +2017,24 @@ void EditorBase::NotifyEditorObservers(
|
|||
}
|
||||
}
|
||||
|
||||
void EditorBase::FireInputEvent() {
|
||||
void EditorBase::FireInputEvent(EditAction aEditAction) {
|
||||
MOZ_ASSERT(IsEditActionDataAvailable());
|
||||
|
||||
// We don't need to dispatch multiple input events if there is a pending
|
||||
// input event. However, it may have different event target. If we resolved
|
||||
// this issue, we need to manage the pending events in an array. But it's
|
||||
// overwork. We don't need to do it for the very rare case.
|
||||
// TODO: However, we start to set InputEvent.inputType. So, each "input"
|
||||
// event now notifies web app each change. So, perhaps, we should
|
||||
// not omit input events.
|
||||
|
||||
RefPtr<Element> targetElement = GetInputEventTargetElement();
|
||||
if (NS_WARN_IF(!targetElement)) {
|
||||
return;
|
||||
}
|
||||
RefPtr<TextEditor> textEditor = AsTextEditor();
|
||||
DebugOnly<nsresult> rvIgnored =
|
||||
nsContentUtils::DispatchInputEvent(targetElement, textEditor);
|
||||
DebugOnly<nsresult> rvIgnored = nsContentUtils::DispatchInputEvent(
|
||||
targetElement, ToInputType(aEditAction), textEditor);
|
||||
NS_WARNING_ASSERTION(NS_SUCCEEDED(rvIgnored),
|
||||
"Failed to dispatch input event");
|
||||
}
|
||||
|
|
|
@ -1771,8 +1771,14 @@ class EditorBase : public nsIEditor,
|
|||
|
||||
nsresult DetermineCurrentDirection();
|
||||
|
||||
/**
|
||||
* FireInputEvent() dispatches an "input" event synchronously or
|
||||
* asynchronously if it's not safe to dispatch.
|
||||
*/
|
||||
MOZ_CAN_RUN_SCRIPT
|
||||
void FireInputEvent();
|
||||
void FireInputEvent() { FireInputEvent(GetEditAction()); }
|
||||
MOZ_CAN_RUN_SCRIPT
|
||||
void FireInputEvent(EditAction aEditAction);
|
||||
|
||||
/**
|
||||
* Called after a transaction is done successfully.
|
||||
|
|
|
@ -306,7 +306,7 @@ nsresult TextEditor::OnDrop(DragEvent* aDropEvent) {
|
|||
|
||||
// Let's fire "input" event for the deletion now.
|
||||
if (mDispatchInputEvent) {
|
||||
FireInputEvent();
|
||||
FireInputEvent(EditAction::eDeleteByDrag);
|
||||
if (NS_WARN_IF(Destroyed())) {
|
||||
return NS_ERROR_EDITOR_DESTROYED;
|
||||
}
|
||||
|
|
|
@ -84,6 +84,8 @@ SimpleTest.waitForFocus(async function() {
|
|||
'"input" event should be never cancelable');
|
||||
is(aEvent.bubbles, true,
|
||||
'"input" event should always bubble');
|
||||
is(aEvent.inputType, "",
|
||||
"inputType should be empty string when an element is moved");
|
||||
}
|
||||
|
||||
content.addEventListener("input", onInput);
|
||||
|
|
|
@ -13,7 +13,7 @@
|
|||
<iframe id="editor1" srcdoc="<html><body contenteditable id='eventTarget'></body></html>"></iframe>
|
||||
<iframe id="editor2" srcdoc="<html contenteditable id='eventTarget'><body></body></html>"></iframe>
|
||||
<iframe id="editor3" srcdoc="<html><body><div contenteditable id='eventTarget'></div></body></html>"></iframe>
|
||||
<iframe id="editor4" srcdoc="<html contenteditable id='eventTarget'><body><div contenteditable id='editTarget'></div></body></html>"></iframe>
|
||||
<iframe id="editor4" srcdoc="<html contenteditable id='eventTarget'><body><div contenteditable></div></body></html>"></iframe>
|
||||
<iframe id="editor5" srcdoc="<html><body id='eventTarget'></body><script>document.designMode='on';</script></html>"></iframe>
|
||||
</div>
|
||||
<div id="content" style="display: none">
|
||||
|
@ -27,6 +27,7 @@
|
|||
SimpleTest.waitForExplicitFinish();
|
||||
SimpleTest.waitForFocus(runTests, window);
|
||||
|
||||
const kIsWin = navigator.platform.indexOf("Win") == 0;
|
||||
const kIsMac = navigator.platform.indexOf("Mac") == 0;
|
||||
|
||||
function runTests() {
|
||||
|
@ -35,6 +36,7 @@ function runTests() {
|
|||
aWindow.focus();
|
||||
|
||||
var body = aDocument.body;
|
||||
var selection = aWindow.getSelection();
|
||||
|
||||
var eventTarget = aDocument.getElementById("eventTarget");
|
||||
// The event target must be focusable because it's the editing host.
|
||||
|
@ -92,31 +94,43 @@ function runTests() {
|
|||
is(editTarget.innerHTML, "a", aDescription + "wrong element was edited");
|
||||
ok(inputEvent, aDescription + "input event wasn't fired by 'a' key");
|
||||
ok(inputEvent.isTrusted, aDescription + "input event by 'a' key wasn't trusted event");
|
||||
is(inputEvent.inputType, "insertText",
|
||||
aDescription + 'inputType should be "insertText" when typing "a"');
|
||||
|
||||
inputEvent = null;
|
||||
synthesizeKey("VK_BACK_SPACE", { }, aWindow);
|
||||
synthesizeKey("KEY_Backspace", { }, aWindow);
|
||||
ok(inputEvent, aDescription + "input event wasn't fired by BackSpace key");
|
||||
ok(inputEvent.isTrusted, aDescription + "input event by BackSpace key wasn't trusted event");
|
||||
is(inputEvent.inputType, "deleteContentBackward",
|
||||
aDescription + 'inputType should be "deleteContentBackward" when pressing "Backspace" with collapsed selection');
|
||||
|
||||
inputEvent = null;
|
||||
synthesizeKey("B", { shiftKey: true }, aWindow);
|
||||
ok(inputEvent, aDescription + "input event wasn't fired by 'B' key");
|
||||
ok(inputEvent.isTrusted, aDescription + "input event by 'B' key wasn't trusted event");
|
||||
is(inputEvent.inputType, "insertText",
|
||||
aDescription + 'inputType should be "insertText" when typing "B"');
|
||||
|
||||
inputEvent = null;
|
||||
synthesizeKey("VK_RETURN", { }, aWindow);
|
||||
synthesizeKey("KEY_Enter", { }, aWindow);
|
||||
ok(inputEvent, aDescription + "input event wasn't fired by Enter key");
|
||||
ok(inputEvent.isTrusted, aDescription + "input event by Enter key wasn't trusted event");
|
||||
is(inputEvent.inputType, "insertParagraph",
|
||||
aDescription + 'inputType should be "insertParagraph" when pressing "Enter"');
|
||||
|
||||
inputEvent = null;
|
||||
synthesizeKey("C", { shiftKey: true }, aWindow);
|
||||
ok(inputEvent, aDescription + "input event wasn't fired by 'C' key");
|
||||
ok(inputEvent.isTrusted, aDescription + "input event by 'C' key wasn't trusted event");
|
||||
is(inputEvent.inputType, "insertText",
|
||||
aDescription + 'inputType should be "insertText" when typing "C"');
|
||||
|
||||
inputEvent = null;
|
||||
synthesizeKey("VK_RETURN", { }, aWindow);
|
||||
synthesizeKey("KEY_Enter", { }, aWindow);
|
||||
ok(inputEvent, aDescription + "input event wasn't fired by Enter key (again)");
|
||||
ok(inputEvent.isTrusted, aDescription + "input event by Enter key (again) wasn't trusted event");
|
||||
is(inputEvent.inputType, "insertParagraph",
|
||||
aDescription + 'inputType should be "insertParagraph" when pressing "Enter" again');
|
||||
|
||||
inputEvent = null;
|
||||
editTarget.innerHTML = "foo-bar";
|
||||
|
@ -130,29 +144,197 @@ function runTests() {
|
|||
synthesizeKey(" ", { }, aWindow);
|
||||
ok(inputEvent, aDescription + "input event wasn't fired by Space key");
|
||||
ok(inputEvent.isTrusted, aDescription + "input event by Space key wasn't trusted event");
|
||||
is(inputEvent.inputType, "insertText",
|
||||
aDescription + 'inputType should be "insertText" when typing " "');
|
||||
|
||||
inputEvent = null;
|
||||
synthesizeKey("VK_DELETE", { }, aWindow);
|
||||
synthesizeKey("KEY_Delete", { }, aWindow);
|
||||
ok(!inputEvent, aDescription + "input event was fired by Delete key at the end");
|
||||
|
||||
inputEvent = null;
|
||||
synthesizeKey("VK_LEFT", { }, aWindow);
|
||||
synthesizeKey("KEY_ArrowLeft", { }, aWindow);
|
||||
ok(!inputEvent, aDescription + "input event was fired by Left key");
|
||||
|
||||
inputEvent = null;
|
||||
synthesizeKey("VK_DELETE", { }, aWindow);
|
||||
synthesizeKey("KEY_Delete", { }, aWindow);
|
||||
ok(inputEvent, aDescription + "input event wasn't fired by Delete key at the start");
|
||||
ok(inputEvent.isTrusted, aDescription + "input event by Delete key wasn't trusted event");
|
||||
is(inputEvent.inputType, "deleteContentForward",
|
||||
aDescription + 'inputType should be "deleteContentForward" when pressing "Delete" with collapsed selection');
|
||||
|
||||
inputEvent = null;
|
||||
synthesizeKey("z", { accelKey: true }, aWindow);
|
||||
ok(inputEvent, aDescription + "input event wasn't fired by Undo");
|
||||
ok(inputEvent.isTrusted, aDescription + "input event by Undo wasn't trusted event");
|
||||
is(inputEvent.inputType, "historyUndo",
|
||||
aDescription + 'inputType should be "historyUndo" when doing "Undo"');
|
||||
|
||||
inputEvent = null;
|
||||
synthesizeKey("z", { accelKey: true, shiftKey: true }, aWindow);
|
||||
ok(inputEvent, aDescription + "input event wasn't fired by Redo");
|
||||
ok(inputEvent.isTrusted, aDescription + "input event by Redo wasn't trusted event");
|
||||
is(inputEvent.inputType, "historyRedo",
|
||||
aDescription + 'inputType should be "historyRedo" when doing "Redo"');
|
||||
|
||||
inputEvent = null;
|
||||
synthesizeKey("KEY_Enter", {shiftKey: true}, aWindow);
|
||||
ok(inputEvent, aDescription + "input event wasn't fired by Shift + Enter key");
|
||||
ok(inputEvent.isTrusted, aDescription + "input event by Shift + Enter key wasn't trusted event");
|
||||
is(inputEvent.inputType, "insertLineBreak",
|
||||
aDescription + 'inputType should be "insertLineBreak" when pressing Shift + "Enter"');
|
||||
|
||||
// Backspace/Delete with non-collapsed selection.
|
||||
editTarget.innerHTML = "a";
|
||||
editTarget.focus();
|
||||
selection.selectAllChildren(editTarget);
|
||||
inputEvent = null;
|
||||
synthesizeKey("KEY_Backspace", {}, aWindow);
|
||||
ok(inputEvent,
|
||||
aDescription + 'input event should be fired by pressing "Backspace" with non-collapsed selection');
|
||||
ok(inputEvent.isTrusted,
|
||||
aDescription + 'input event should be trusted when pressing "Backspace" with non-collapsed selection');
|
||||
is(inputEvent.inputType, "deleteContentBackward",
|
||||
aDescription + 'inputType should be "deleteContentBackward" when pressing "Backspace" with non-collapsed selection');
|
||||
|
||||
editTarget.innerHTML = "a";
|
||||
editTarget.focus();
|
||||
selection.selectAllChildren(editTarget);
|
||||
inputEvent = null;
|
||||
synthesizeKey("KEY_Delete", {}, aWindow);
|
||||
ok(inputEvent,
|
||||
aDescription + 'input event should be fired by pressing "Delete" with non-collapsed selection');
|
||||
ok(inputEvent.isTrusted,
|
||||
aDescription + 'input event should be trusted when pressing "Delete" with non-collapsed selection');
|
||||
is(inputEvent.inputType, "deleteContentForward",
|
||||
aDescription + 'inputType should be "deleteContentBackward" when Delete "Backspace" with non-collapsed selection');
|
||||
|
||||
// Delete to previous/next word boundary with collapsed selection.
|
||||
editTarget.innerHTML = "a";
|
||||
editTarget.focus();
|
||||
selection.selectAllChildren(editTarget);
|
||||
selection.collapseToEnd();
|
||||
inputEvent = null;
|
||||
SpecialPowers.doCommand(aWindow, "cmd_deleteWordBackward");
|
||||
ok(inputEvent,
|
||||
aDescription + "input event should be fired by deleting to previous word boundary with collapsed selection");
|
||||
ok(inputEvent.isTrusted,
|
||||
aDescription + "input event should be trusted when deleting to previous word boundary with collapsed selection");
|
||||
is(inputEvent.inputType, "deleteWordBackward",
|
||||
aDescription + 'inputType should be "deleteWordBackward" when deleting to previous word boundary with collapsed selection');
|
||||
|
||||
editTarget.innerHTML = "a";
|
||||
editTarget.focus();
|
||||
selection.selectAllChildren(editTarget);
|
||||
selection.collapseToStart();
|
||||
inputEvent = null;
|
||||
SpecialPowers.doCommand(aWindow, "cmd_deleteWordForward");
|
||||
ok(inputEvent,
|
||||
aDescription + "input event should be fired by deleting to next word boundary with collapsed selection");
|
||||
ok(inputEvent.isTrusted,
|
||||
aDescription + "input event should be trusted when deleting to next word boundary with collapsed selection");
|
||||
is(inputEvent.inputType, "deleteWordForward",
|
||||
aDescription + 'inputType should be "deleteWordForward" when deleting to next word boundary with collapsed selection');
|
||||
|
||||
// Delete to previous/next word boundary with non-collapsed selection.
|
||||
editTarget.innerHTML = "abc";
|
||||
editTarget.focus();
|
||||
selection.setBaseAndExtent(editTarget.firstChild, 1, editTarget.firstChild, 2);
|
||||
inputEvent = null;
|
||||
SpecialPowers.doCommand(aWindow, "cmd_deleteWordBackward");
|
||||
ok(inputEvent,
|
||||
aDescription + "input event should be fired by deleting to previous word boundary with non-collapsed selection");
|
||||
ok(inputEvent.isTrusted,
|
||||
aDescription + "input event should be trusted when deleting to previous word boundary with non-collapsed selection");
|
||||
if (kIsWin) {
|
||||
// Only on Windows, we collapse selection to start before handling this command.
|
||||
is(inputEvent.inputType, "deleteWordBackward",
|
||||
aDescription + 'inputType should be "deleteWordBackward" when deleting to previous word boundary with non-collapsed selection');
|
||||
} else {
|
||||
is(inputEvent.inputType, "deleteContentBackward",
|
||||
aDescription + 'inputType should be "deleteContentBackward" when deleting to previous word boundary with non-collapsed selection');
|
||||
}
|
||||
|
||||
editTarget.innerHTML = "abc";
|
||||
editTarget.focus();
|
||||
selection.setBaseAndExtent(editTarget.firstChild, 1, editTarget.firstChild, 2);
|
||||
inputEvent = null;
|
||||
SpecialPowers.doCommand(aWindow, "cmd_deleteWordForward");
|
||||
ok(inputEvent,
|
||||
aDescription + "input event should be fired by deleting to next word boundary with non-collapsed selection");
|
||||
ok(inputEvent.isTrusted,
|
||||
aDescription + "input event should be trusted when deleting to next word boundary with non-collapsed selection");
|
||||
if (kIsWin) {
|
||||
// Only on Windows, we collapse selection to start before handling this command.
|
||||
is(inputEvent.inputType, "deleteWordForward",
|
||||
aDescription + 'inputType should be "deleteWordForward" when deleting to next word boundary with non-collapsed selection');
|
||||
} else {
|
||||
is(inputEvent.inputType, "deleteContentForward",
|
||||
aDescription + 'inputType should be "deleteContentForward" when deleting to next word boundary with non-collapsed selection');
|
||||
}
|
||||
|
||||
// Delete to previous/next visual line boundary with collapsed selection.
|
||||
editTarget.innerHTML = "a";
|
||||
editTarget.focus();
|
||||
selection.selectAllChildren(editTarget);
|
||||
selection.collapseToEnd();
|
||||
inputEvent = null;
|
||||
SpecialPowers.doCommand(aWindow, "cmd_deleteToBeginningOfLine");
|
||||
ok(inputEvent,
|
||||
aDescription + "input event should be fired by deleting to previous visual line boundary with collapsed selection");
|
||||
ok(inputEvent.isTrusted,
|
||||
aDescription + "input event should be trusted when deleting to previous visual line boundary with collapsed selection");
|
||||
is(inputEvent.inputType, "deleteSoftLineBackward",
|
||||
aDescription + 'inputType should be "deleteSoftLineBackward" when deleting to previous visual line boundary with collapsed selection');
|
||||
|
||||
editTarget.innerHTML = "a";
|
||||
editTarget.focus();
|
||||
selection.selectAllChildren(editTarget);
|
||||
selection.collapseToStart();
|
||||
inputEvent = null;
|
||||
SpecialPowers.doCommand(aWindow, "cmd_deleteToEndOfLine");
|
||||
ok(inputEvent,
|
||||
aDescription + "input event should be fired by deleting to next visual line boundary with collapsed selection");
|
||||
ok(inputEvent.isTrusted,
|
||||
aDescription + "input event should be trusted when deleting to next visual line boundary with collapsed selection");
|
||||
is(inputEvent.inputType, "deleteSoftLineForward",
|
||||
aDescription + 'inputType should be "deleteSoftLineForward" when deleting to visual line boundary with collapsed selection');
|
||||
|
||||
// Delete to previous/next visual line boundary with non-collapsed selection.
|
||||
editTarget.innerHTML = "abc";
|
||||
editTarget.focus();
|
||||
selection.setBaseAndExtent(editTarget.firstChild, 1, editTarget.firstChild, 2);
|
||||
inputEvent = null;
|
||||
SpecialPowers.doCommand(aWindow, "cmd_deleteToBeginningOfLine");
|
||||
ok(inputEvent,
|
||||
aDescription + "input event should be fired by deleting to previous visual line boundary with non-collapsed selection");
|
||||
ok(inputEvent.isTrusted,
|
||||
aDescription + "input event should be trusted when deleting to previous visual line boundary with non-collapsed selection");
|
||||
if (kIsWin) {
|
||||
// Only on Windows, we collapse selection to start before handling this command.
|
||||
is(inputEvent.inputType, "deleteSoftLineBackward",
|
||||
aDescription + 'inputType should be "deleteSoftLineBackward" when deleting to next visual line boundary with non-collapsed selection');
|
||||
} else {
|
||||
is(inputEvent.inputType, "deleteContentBackward",
|
||||
aDescription + 'inputType should be "deleteContentBackward" when deleting to previous visual line boundary with non-collapsed selection');
|
||||
}
|
||||
|
||||
editTarget.innerHTML = "abc";
|
||||
editTarget.focus();
|
||||
selection.setBaseAndExtent(editTarget.firstChild, 1, editTarget.firstChild, 2);
|
||||
inputEvent = null;
|
||||
SpecialPowers.doCommand(aWindow, "cmd_deleteToEndOfLine");
|
||||
ok(inputEvent,
|
||||
aDescription + "input event should be fired by deleting to next visual line boundary with non-collapsed selection");
|
||||
ok(inputEvent.isTrusted,
|
||||
aDescription + "input event should be trusted when deleting to next visual line boundary with non-collapsed selection");
|
||||
if (kIsWin) {
|
||||
// Only on Windows, we collapse selection to start before handling this command.
|
||||
is(inputEvent.inputType, "deleteSoftLineForward",
|
||||
aDescription + 'inputType should be "deleteSoftLineForward" when deleting to next visual line boundary with non-collapsed selection');
|
||||
} else {
|
||||
is(inputEvent.inputType, "deleteContentForward",
|
||||
aDescription + 'inputType should be "deleteContentForward" when deleting to next visual line boundary with non-collapsed selection');
|
||||
}
|
||||
|
||||
aWindow.removeEventListener("input", handler, true);
|
||||
}
|
||||
|
|
|
@ -66,12 +66,16 @@ function runTests() {
|
|||
is(aElement.value, "a", aDescription + "'a' key didn't change the value");
|
||||
ok(inputEvent, aDescription + "input event wasn't fired by 'a' key");
|
||||
ok(inputEvent.isTrusted, aDescription + "input event by 'a' key wasn't trusted event");
|
||||
is(inputEvent.inputType, "insertText",
|
||||
aDescription + 'inputType should be "insertText" when typing "a"');
|
||||
|
||||
inputEvent = null;
|
||||
synthesizeKey("KEY_Backspace");
|
||||
is(aElement.value, "", aDescription + "BackSpace key didn't remove the value");
|
||||
ok(inputEvent, aDescription + "input event wasn't fired by BackSpace key");
|
||||
ok(inputEvent.isTrusted, aDescription + "input event by BackSpace key wasn't trusted event");
|
||||
is(inputEvent.inputType, "deleteContentBackward",
|
||||
aDescription + 'inputType should be "deleteContentBackward" when pressing "Backspace" with collapsed selection');
|
||||
|
||||
if (aIsTextarea) {
|
||||
inputEvent = null;
|
||||
|
@ -79,6 +83,8 @@ function runTests() {
|
|||
is(aElement.value, "\n", aDescription + "Enter key didn't change the value");
|
||||
ok(inputEvent, aDescription + "input event wasn't fired by Enter key");
|
||||
ok(inputEvent.isTrusted, aDescription + "input event by Enter key wasn't trusted event");
|
||||
is(inputEvent.inputType, "insertLineBreak",
|
||||
aDescription + 'inputType should be "insertLineBreak" when pressing "Enter"');
|
||||
}
|
||||
|
||||
inputEvent = null;
|
||||
|
@ -96,6 +102,8 @@ function runTests() {
|
|||
is(aElement.value, " ", aDescription + "Space key didn't change the value");
|
||||
ok(inputEvent, aDescription + "input event wasn't fired by Space key");
|
||||
ok(inputEvent.isTrusted, aDescription + "input event by Space key wasn't trusted event");
|
||||
is(inputEvent.inputType, "insertText",
|
||||
aDescription + 'inputType should be "insertText" when typing " "');
|
||||
|
||||
inputEvent = null;
|
||||
synthesizeKey("KEY_Delete");
|
||||
|
@ -112,18 +120,47 @@ function runTests() {
|
|||
is(aElement.value, "", aDescription + "Delete key didn't remove the value");
|
||||
ok(inputEvent, aDescription + "input event wasn't fired by Delete key at the start");
|
||||
ok(inputEvent.isTrusted, aDescription + "input event by Delete key wasn't trusted event");
|
||||
is(inputEvent.inputType, "deleteContentForward",
|
||||
aDescription + 'inputType should be "deleteContentForward" when pressing "Delete" with collapsed selection');
|
||||
|
||||
inputEvent = null;
|
||||
synthesizeKey("z", {accelKey: true});
|
||||
is(aElement.value, " ", aDescription + "Accel+Z key didn't undo the value");
|
||||
ok(inputEvent, aDescription + "input event wasn't fired by Undo");
|
||||
ok(inputEvent.isTrusted, aDescription + "input event by Undo wasn't trusted event");
|
||||
is(inputEvent.inputType, "historyUndo",
|
||||
aDescription + 'inputType should be "historyUndo" when doing "Undo"');
|
||||
|
||||
inputEvent = null;
|
||||
synthesizeKey("Z", {accelKey: true, shiftKey: true});
|
||||
is(aElement.value, "", aDescription + "Accel+Y key didn't redo the value");
|
||||
ok(inputEvent, aDescription + "input event wasn't fired by Redo");
|
||||
ok(inputEvent.isTrusted, aDescription + "input event by Redo wasn't trusted event");
|
||||
is(inputEvent.inputType, "historyRedo",
|
||||
aDescription + 'inputType should be "historyRedo" when doing "Redo"');
|
||||
|
||||
// Backspace/Delete with non-collapsed selection.
|
||||
aElement.value = "a";
|
||||
aElement.select();
|
||||
inputEvent = null;
|
||||
synthesizeKey("KEY_Backspace");
|
||||
ok(inputEvent,
|
||||
aDescription + 'input event should be fired by pressing "Backspace" with non-collapsed selection');
|
||||
ok(inputEvent.isTrusted,
|
||||
aDescription + 'input event should be trusted when pressing "Backspace" with non-collapsed selection');
|
||||
is(inputEvent.inputType, "deleteContentBackward",
|
||||
aDescription + 'inputType should be "deleteContentBackward" when pressing "Backspace" with non-collapsed selection');
|
||||
|
||||
aElement.value = "a";
|
||||
aElement.select();
|
||||
inputEvent = null;
|
||||
synthesizeKey("KEY_Delete");
|
||||
ok(inputEvent,
|
||||
aDescription + 'input event should be fired by pressing "Delete" with non-collapsed selection');
|
||||
ok(inputEvent.isTrusted,
|
||||
aDescription + 'input event should be trusted when pressing "Delete" with non-collapsed selection');
|
||||
is(inputEvent.inputType, "deleteContentForward",
|
||||
aDescription + 'inputType should be "deleteContentBackward" when Delete "Backspace" with non-collapsed selection');
|
||||
|
||||
aElement.removeEventListener("input", handler, true);
|
||||
}
|
||||
|
|
|
@ -29,6 +29,7 @@ function checkInputEvent(aEvent, aExpectedTarget, aDescription) {
|
|||
is(aEvent.cancelable, false, `"input" event should be never cancelable ${aDescription}`);
|
||||
is(aEvent.bubbles, true, `"input" event should always bubble ${aDescription}`);
|
||||
is(aEvent.target, aExpectedTarget, `"input" event should be fired on the <${aExpectedTarget.tagName.toLowerCase()}> element ${aDescription}`);
|
||||
is(aEvent.inputType, "insertFromDrop", `inputType should be "insertFromDrop" on the <${aExpectedTarget.tagName.toLowerCase()}> element ${aDescription}`);
|
||||
}
|
||||
|
||||
function doTest() {
|
||||
|
|
|
@ -83,6 +83,8 @@ function checkInputEvent(aEvent, aDescription) {
|
|||
`"input" event should be never cancelable ${aDescription}`);
|
||||
is(aEvent.bubbles, true,
|
||||
`"input" event should always bubble ${aDescription}`);
|
||||
is(aEvent.inputType, "insertFromPaste",
|
||||
`inputType should be "insertFromPaste" ${aDescription}`);
|
||||
}
|
||||
|
||||
async function doTextareaTests(aTextarea) {
|
||||
|
|
|
@ -51,6 +51,8 @@ SimpleTest.waitForFocus(function() {
|
|||
'"input" event should be never cancelable even if "click" event after calling nsIEditorMailSupport.insertAsCitedQuotation() of plaintext editor');
|
||||
is(inputEvents[0].bubbles, true,
|
||||
'"input" event should always bubble after calling nsIEditorMailSupport.insertAsCitedQuotation() of plaintext editor');
|
||||
is(inputEvents[0].inputType, "insertText",
|
||||
'inputType should be "insertText" after calling nsIEditorMailSupport.insertAsCitedQuotation() of plaintext editor');
|
||||
|
||||
// Tests when the editor is in HTML editor mode.
|
||||
getEditor().flags &= ~SpecialPowers.Ci.nsIPlaintextEditor.eEditorPlaintextMask;
|
||||
|
@ -76,6 +78,8 @@ SimpleTest.waitForFocus(function() {
|
|||
'"input" event should be never cancelable even if "click" event after calling nsIEditorMailSupport.insertAsCitedQuotation() of HTMLEditor editor (inserting as plaintext)');
|
||||
is(inputEvents[0].bubbles, true,
|
||||
'"input" event should always bubble after calling nsIEditorMailSupport.insertAsCitedQuotation() of HTMLEditor editor (inserting as plaintext)');
|
||||
is(inputEvents[0].inputType, "",
|
||||
"inputType should be empty string after calling nsIEditorMailSupport.insertAsCitedQuotation() of HTMLEditor editor (inserting as plaintext)");
|
||||
|
||||
editor.innerHTML = "";
|
||||
|
||||
|
@ -98,6 +102,8 @@ SimpleTest.waitForFocus(function() {
|
|||
'"input" event should be never cancelable even if "click" event after calling nsIEditorMailSupport.insertAsCitedQuotation() of HTMLEditor editor (inserting as HTML source)');
|
||||
is(inputEvents[0].bubbles, true,
|
||||
'"input" event should always bubble after calling nsIEditorMailSupport.insertAsCitedQuotation() of HTMLEditor editor (inserting as HTML source)');
|
||||
is(inputEvents[0].inputType, "",
|
||||
"inputType should be empty string after calling nsIEditorMailSupport.insertAsCitedQuotation() of HTMLEditor editor (inserting as HTML source)");
|
||||
|
||||
SimpleTest.finish();
|
||||
});
|
||||
|
|
|
@ -25,13 +25,14 @@ SimpleTest.waitForFocus(function() {
|
|||
}
|
||||
editor.addEventListener("input", onInput);
|
||||
|
||||
function checkInputEvent(aEvent, aDescription) {
|
||||
function checkInputEvent(aEvent, aInputType, aDescription) {
|
||||
if (aEvent.type != "input") {
|
||||
return;
|
||||
}
|
||||
ok(aEvent instanceof InputEvent, `${aDescription}"input" event should be dispatched with InputEvent interface`);
|
||||
is(aEvent.cancelable, false, `${aDescription}"input" event should be never cancelable`);
|
||||
is(aEvent.bubbles, true, `${aDescription}"input" event should always bubble`);
|
||||
is(aEvent.inputType, aInputType, `${aDescription}inputType should be ${aInputType}`);
|
||||
}
|
||||
|
||||
function selectFromTextSiblings(aNode) {
|
||||
|
@ -70,7 +71,7 @@ SimpleTest.waitForFocus(function() {
|
|||
is(inputEvents.length, 1,
|
||||
description + condition + ': nsIHTMLEditor.removeInlineProperty("b", "") should cause an "input" event');
|
||||
if (inputEvents.length > 0) {
|
||||
checkInputEvent(inputEvents[0], description);
|
||||
checkInputEvent(inputEvents[0], "formatBold", description);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -86,7 +87,7 @@ SimpleTest.waitForFocus(function() {
|
|||
is(inputEvents.length, 1,
|
||||
description + condition + ': nsIHTMLEditor.removeInlineProperty("b", "") should cause an "input" event');
|
||||
if (inputEvents.length > 0) {
|
||||
checkInputEvent(inputEvents[0], description);
|
||||
checkInputEvent(inputEvents[0], "formatBold", description);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -102,7 +103,7 @@ SimpleTest.waitForFocus(function() {
|
|||
is(inputEvents.length, 1,
|
||||
description + condition + ': nsIHTMLEditor.removeInlineProperty("b", "") should cause an "input" event');
|
||||
if (inputEvents.length > 0) {
|
||||
checkInputEvent(inputEvents[0], description);
|
||||
checkInputEvent(inputEvents[0], "formatBold", description);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -118,7 +119,7 @@ SimpleTest.waitForFocus(function() {
|
|||
is(inputEvents.length, 1,
|
||||
description + condition + ': nsIHTMLEditor.removeInlineProperty("b", "") should cause an "input" event');
|
||||
if (inputEvents.length > 0) {
|
||||
checkInputEvent(inputEvents[0], description);
|
||||
checkInputEvent(inputEvents[0], "formatBold", description);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -134,7 +135,7 @@ SimpleTest.waitForFocus(function() {
|
|||
is(inputEvents.length, 1,
|
||||
description + condition + ': nsIHTMLEditor.removeInlineProperty("i", "") should cause an "input" event');
|
||||
if (inputEvents.length > 0) {
|
||||
checkInputEvent(inputEvents[0], description);
|
||||
checkInputEvent(inputEvents[0], "formatItalic", description);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -150,7 +151,7 @@ SimpleTest.waitForFocus(function() {
|
|||
is(inputEvents.length, 1,
|
||||
description + condition + ': nsIHTMLEditor.removeInlineProperty("b", "") should cause an "input" event');
|
||||
if (inputEvents.length > 0) {
|
||||
checkInputEvent(inputEvents[0], description);
|
||||
checkInputEvent(inputEvents[0], "formatBold", description);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -166,7 +167,7 @@ SimpleTest.waitForFocus(function() {
|
|||
is(inputEvents.length, 1,
|
||||
description + condition + ': nsIHTMLEditor.removeInlineProperty("i", "") should cause an "input" event');
|
||||
if (inputEvents.length > 0) {
|
||||
checkInputEvent(inputEvents[0], description);
|
||||
checkInputEvent(inputEvents[0], "formatItalic", description);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -182,7 +183,7 @@ SimpleTest.waitForFocus(function() {
|
|||
is(inputEvents.length, 1,
|
||||
description + condition + ': nsIHTMLEditor.removeInlineProperty("i", "") should cause an "input" event');
|
||||
if (inputEvents.length > 0) {
|
||||
checkInputEvent(inputEvents[0], description);
|
||||
checkInputEvent(inputEvents[0], "formatItalic", description);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -198,7 +199,7 @@ SimpleTest.waitForFocus(function() {
|
|||
is(inputEvents.length, 1,
|
||||
description + condition + ': nsIHTMLEditor.removeInlineProperty("b", "") should cause an "input" event');
|
||||
if (inputEvents.length > 0) {
|
||||
checkInputEvent(inputEvents[0], description);
|
||||
checkInputEvent(inputEvents[0], "formatBold", description);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -214,7 +215,7 @@ SimpleTest.waitForFocus(function() {
|
|||
is(inputEvents.length, 1,
|
||||
description + condition + ': nsIHTMLEditor.removeInlineProperty("href", "") should cause an "input" event');
|
||||
if (inputEvents.length > 0) {
|
||||
checkInputEvent(inputEvents[0], description);
|
||||
checkInputEvent(inputEvents[0], "", description);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -231,7 +232,7 @@ SimpleTest.waitForFocus(function() {
|
|||
is(inputEvents.length, 1,
|
||||
description + condition + ': nsIHTMLEditor.removeInlineProperty("href", "") should cause an "input" event');
|
||||
if (inputEvents.length > 0) {
|
||||
checkInputEvent(inputEvents[0], description);
|
||||
checkInputEvent(inputEvents[0], "", description);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -273,7 +274,7 @@ SimpleTest.waitForFocus(function() {
|
|||
is(inputEvents.length, 1,
|
||||
description + condition + ': nsIHTMLEditor.removeInlineProperty("name", "") should cause an "input" event');
|
||||
if (inputEvents.length > 0) {
|
||||
checkInputEvent(inputEvents[0], description);
|
||||
checkInputEvent(inputEvents[0], "", description);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -53,6 +53,8 @@ SimpleTest.waitForFocus(function() {
|
|||
'"input" event should be never cancelable even if "click" event (on multi-line editor)');
|
||||
is(inputEvents[0].bubbles, true,
|
||||
'"input" event should always bubble (on multi-line editor)');
|
||||
is(inputEvents[0].inputType, "insertLineBreak",
|
||||
'inputType should be "insertLineBreak" on multi-line editor');
|
||||
|
||||
// Note that despite of the name, insertLineBreak() should insert paragraph separator in HTMLEditor.
|
||||
|
||||
|
@ -76,6 +78,8 @@ SimpleTest.waitForFocus(function() {
|
|||
'"input" event should be never cancelable even if "click" event (when defaultParagraphSeparator is "br") #1');
|
||||
is(inputEvents[0].bubbles, true,
|
||||
'"input" event should always bubble (when defaultParagraphSeparator is "br") #1');
|
||||
is(inputEvents[0].inputType, "insertParagraph",
|
||||
'inputType should be "insertParagraph" on HTMLEditor (when defaultParagraphSeparator is "br") #1');
|
||||
|
||||
contenteditable.innerHTML = "<p>abcdef</p>";
|
||||
contenteditable.focus();
|
||||
|
@ -95,6 +99,8 @@ SimpleTest.waitForFocus(function() {
|
|||
'"input" event should be never cancelable even if "click" event (when defaultParagraphSeparator is "br") #2');
|
||||
is(inputEvents[0].bubbles, true,
|
||||
'"input" event should always bubble (when defaultParagraphSeparator is "br") #2');
|
||||
is(inputEvents[0].inputType, "insertParagraph",
|
||||
'inputType should be "insertParagraph" on HTMLEditor (when defaultParagraphSeparator is "br") #2');
|
||||
|
||||
contenteditable.innerHTML = "<div>abcdef</div>";
|
||||
contenteditable.focus();
|
||||
|
@ -114,6 +120,8 @@ SimpleTest.waitForFocus(function() {
|
|||
'"input" event should be never cancelable even if "click" event (when defaultParagraphSeparator is "br") #3');
|
||||
is(inputEvents[0].bubbles, true,
|
||||
'"input" event should always bubble (when defaultParagraphSeparator is "br") #3');
|
||||
is(inputEvents[0].inputType, "insertParagraph",
|
||||
'inputType should be "insertParagraph" on HTMLEditor (when defaultParagraphSeparator is "br") #3');
|
||||
|
||||
contenteditable.innerHTML = "<pre>abcdef</pre>";
|
||||
contenteditable.focus();
|
||||
|
@ -133,6 +141,8 @@ SimpleTest.waitForFocus(function() {
|
|||
'"input" event should be never cancelable even if "click" event (when defaultParagraphSeparator is "br") #4');
|
||||
is(inputEvents[0].bubbles, true,
|
||||
'"input" event should always bubble (when defaultParagraphSeparator is "br") #4');
|
||||
is(inputEvents[0].inputType, "insertParagraph",
|
||||
'inputType should be "insertParagraph" on HTMLEditor (when defaultParagraphSeparator is "br") #4');
|
||||
|
||||
document.execCommand("defaultParagraphSeparator", false, "p");
|
||||
|
||||
|
@ -154,6 +164,8 @@ SimpleTest.waitForFocus(function() {
|
|||
'"input" event should be never cancelable even if "click" event (when defaultParagraphSeparator is "p") #1');
|
||||
is(inputEvents[0].bubbles, true,
|
||||
'"input" event should always bubble (when defaultParagraphSeparator is "p") #1');
|
||||
is(inputEvents[0].inputType, "insertParagraph",
|
||||
'inputType should be "insertParagraph" on HTMLEditor (when defaultParagraphSeparator is "p") #1');
|
||||
|
||||
contenteditable.innerHTML = "<p>abcdef</p>";
|
||||
contenteditable.focus();
|
||||
|
@ -173,6 +185,8 @@ SimpleTest.waitForFocus(function() {
|
|||
'"input" event should be never cancelable even if "click" event (when defaultParagraphSeparator is "p") #2');
|
||||
is(inputEvents[0].bubbles, true,
|
||||
'"input" event should always bubble (when defaultParagraphSeparator is "p") #2');
|
||||
is(inputEvents[0].inputType, "insertParagraph",
|
||||
'inputType should be "insertParagraph" on HTMLEditor (when defaultParagraphSeparator is "p") #2');
|
||||
|
||||
contenteditable.innerHTML = "<div>abcdef</div>";
|
||||
contenteditable.focus();
|
||||
|
@ -192,6 +206,8 @@ SimpleTest.waitForFocus(function() {
|
|||
'"input" event should be never cancelable even if "click" event (when defaultParagraphSeparator is "p") #3');
|
||||
is(inputEvents[0].bubbles, true,
|
||||
'"input" event should always bubble (when defaultParagraphSeparator is "p") #3');
|
||||
is(inputEvents[0].inputType, "insertParagraph",
|
||||
'inputType should be "insertParagraph" on HTMLEditor (when defaultParagraphSeparator is "p") #3');
|
||||
|
||||
contenteditable.innerHTML = "<pre>abcdef</pre>";
|
||||
contenteditable.focus();
|
||||
|
@ -211,6 +227,8 @@ SimpleTest.waitForFocus(function() {
|
|||
'"input" event should be never cancelable even if "click" event (when defaultParagraphSeparator is "p") #4');
|
||||
is(inputEvents[0].bubbles, true,
|
||||
'"input" event should always bubble (when defaultParagraphSeparator is "p") #4');
|
||||
is(inputEvents[0].inputType, "insertParagraph",
|
||||
'inputType should be "insertParagraph" on HTMLEditor (when defaultParagraphSeparator is "p") #4');
|
||||
|
||||
document.execCommand("defaultParagraphSeparator", false, "div");
|
||||
|
||||
|
@ -232,6 +250,8 @@ SimpleTest.waitForFocus(function() {
|
|||
'"input" event should be never cancelable even if "click" event (when defaultParagraphSeparator is "div") #1');
|
||||
is(inputEvents[0].bubbles, true,
|
||||
'"input" event should always bubble (when defaultParagraphSeparator is "div") #1');
|
||||
is(inputEvents[0].inputType, "insertParagraph",
|
||||
'inputType should be "insertParagraph" on HTMLEditor (when defaultParagraphSeparator is "div") #1');
|
||||
|
||||
contenteditable.innerHTML = "<p>abcdef</p>";
|
||||
contenteditable.focus();
|
||||
|
@ -251,6 +271,8 @@ SimpleTest.waitForFocus(function() {
|
|||
'"input" event should be never cancelable even if "click" event (when defaultParagraphSeparator is "div") #2');
|
||||
is(inputEvents[0].bubbles, true,
|
||||
'"input" event should always bubble (when defaultParagraphSeparator is "div") #2');
|
||||
is(inputEvents[0].inputType, "insertParagraph",
|
||||
'inputType should be "insertParagraph" on HTMLEditor (when defaultParagraphSeparator is "div") #2');
|
||||
|
||||
contenteditable.innerHTML = "<div>abcdef</div>";
|
||||
contenteditable.focus();
|
||||
|
@ -270,6 +292,8 @@ SimpleTest.waitForFocus(function() {
|
|||
'"input" event should be never cancelable even if "click" event (when defaultParagraphSeparator is "div") #3');
|
||||
is(inputEvents[0].bubbles, true,
|
||||
'"input" event should always bubble (when defaultParagraphSeparator is "div") #3');
|
||||
is(inputEvents[0].inputType, "insertParagraph",
|
||||
'inputType should be "insertParagraph" on HTMLEditor (when defaultParagraphSeparator is "div") #3');
|
||||
|
||||
contenteditable.innerHTML = "<pre>abcdef</pre>";
|
||||
contenteditable.focus();
|
||||
|
@ -289,6 +313,8 @@ SimpleTest.waitForFocus(function() {
|
|||
'"input" event should be never cancelable even if "click" event (when defaultParagraphSeparator is "div") #4');
|
||||
is(inputEvents[0].bubbles, true,
|
||||
'"input" event should always bubble (when defaultParagraphSeparator is "div") #4');
|
||||
is(inputEvents[0].inputType, "insertParagraph",
|
||||
'inputType should be "insertParagraph" on HTMLEditor (when defaultParagraphSeparator is "div") #4');
|
||||
|
||||
SimpleTest.finish();
|
||||
});
|
||||
|
|
|
@ -26,6 +26,8 @@ SimpleTest.waitForFocus(function() {
|
|||
`"input" event should be never cancelable ${aDescription}`);
|
||||
is(aEvent.bubbles, true,
|
||||
`"input" event should always bubble ${aDescription}`);
|
||||
is(aEvent.inputType, "deleteContent",
|
||||
`inputType should be "deleteContent" ${aDescription}`);
|
||||
}
|
||||
|
||||
let inputEvents = [];
|
||||
|
|
|
@ -26,6 +26,8 @@ SimpleTest.waitForFocus(function() {
|
|||
`"input" event should be never cancelable ${aDescription}`);
|
||||
is(aEvent.bubbles, true,
|
||||
`"input" event should always bubble ${aDescription}`);
|
||||
is(aEvent.inputType, "deleteContent",
|
||||
`inputType should be "deleteContent" ${aDescription}`);
|
||||
}
|
||||
|
||||
let inputEvents = [];
|
||||
|
|
|
@ -19,17 +19,40 @@ SimpleTest.waitForFocus(function() {
|
|||
let editor = document.getElementById("content");
|
||||
let selection = document.getSelection();
|
||||
|
||||
function checkInputEvent(aEvent, aDescription) {
|
||||
ok(aEvent instanceof InputEvent,
|
||||
`"input" event should be dispatched with InputEvent interface ${aDescription}`);
|
||||
is(aEvent.cancelable, false,
|
||||
`"input" event should be never cancelable ${aDescription}`);
|
||||
is(aEvent.bubbles, true,
|
||||
`"input" event should always bubble ${aDescription}`);
|
||||
is(aEvent.inputType, "deleteContent",
|
||||
`inputType should be "deleteContent" ${aDescription}`);
|
||||
}
|
||||
|
||||
let inputEvents = [];
|
||||
function onInput(aEvent) {
|
||||
inputEvents.push(aEvent);
|
||||
}
|
||||
editor.addEventListener("input", onInput);
|
||||
|
||||
inputEvents = [];
|
||||
selection.collapse(editor.firstChild, 0);
|
||||
getTableEditor().deleteTableColumn(1);
|
||||
is(editor.innerHTML, "out of table<table><tbody><tr><td>default content</td></tr></tbody></table>",
|
||||
"nsITableEditor.deleteTableColumn(1) should do nothing if selection is not in <table>");
|
||||
is(inputEvents.length, 0,
|
||||
'No "input" event should be fired when a call of nsITableEditor.deleteTableColumn(1) does nothing');
|
||||
|
||||
selection.removeAllRanges();
|
||||
try {
|
||||
inputEvents = [];
|
||||
getTableEditor().deleteTableColumn(1);
|
||||
ok(false, "getTableEditor().deleteTableColumn(1) without selection ranges should throw exception");
|
||||
} catch (e) {
|
||||
ok(true, "getTableEditor().deleteTableColumn(1) without selection ranges should throw exception");
|
||||
is(inputEvents.length, 0,
|
||||
'No "input" event should be fired when nsITableEditor.deleteTableColumn(1) causes exception due to no selection range');
|
||||
}
|
||||
|
||||
// If a cell is selected and the argument is less than number of rows,
|
||||
|
@ -39,68 +62,93 @@ SimpleTest.waitForFocus(function() {
|
|||
selection.removeAllRanges();
|
||||
editor.innerHTML =
|
||||
'<table><tr><td id="select">cell1-1</td><td>cell1-2</td></tr><tr><td>cell2-1</td><td>cell2-2</td></tr></table>';
|
||||
inputEvents = [];
|
||||
let range = document.createRange();
|
||||
range.selectNode(document.getElementById("select"));
|
||||
selection.addRange(range);
|
||||
getTableEditor().deleteTableColumn(1);
|
||||
is(editor.innerHTML, "<table><tbody><tr><td>cell1-2</td></tr><tr><td>cell2-2</td></tr></tbody></table>",
|
||||
"nsITableEditor.deleteTableColumn(1) should delete the first column when a cell in the first column is selected");
|
||||
is(inputEvents.length, 1,
|
||||
'Only one "input" event should be fired when a cell in the first column is selected');
|
||||
checkInputEvent(inputEvents[0], "when a cell in the first column is selected");
|
||||
|
||||
selection.removeAllRanges();
|
||||
editor.innerHTML =
|
||||
'<table><tr><td>cell1-1</td><td>cell1-2</td></tr><tr><td>cell2-1</td><td id="select">cell2-2</td></tr></table>';
|
||||
inputEvents = [];
|
||||
range = document.createRange();
|
||||
range.selectNode(document.getElementById("select"));
|
||||
selection.addRange(range);
|
||||
getTableEditor().deleteTableColumn(1);
|
||||
is(editor.innerHTML, "<table><tbody><tr><td>cell1-1</td></tr><tr><td>cell2-1</td></tr></tbody></table>",
|
||||
"nsITableEditor.deleteTableColumn(1) should delete the second column when a cell in the second column is selected");
|
||||
is(inputEvents.length, 1,
|
||||
'Only one "input" event should be fired when a cell in the second column is selected');
|
||||
checkInputEvent(inputEvents[0], "when a cell in the second column is selected");
|
||||
|
||||
selection.removeAllRanges();
|
||||
editor.innerHTML =
|
||||
'<table><tr><td id="select">cell1-1</td><td>cell1-2</td></tr><tr><td>cell2-1</td><td>cell2-2</td></tr></table>';
|
||||
inputEvents = [];
|
||||
range = document.createRange();
|
||||
range.selectNode(document.getElementById("select"));
|
||||
selection.addRange(range);
|
||||
getTableEditor().deleteTableColumn(2);
|
||||
is(editor.innerHTML, "",
|
||||
"nsITableEditor.deleteTableColumn(2) should delete the <table> since there is only 2 columns");
|
||||
is(inputEvents.length, 1,
|
||||
'Only one "input" event should be fired when a cell in first column is selected and argument is same as number of rows');
|
||||
checkInputEvent(inputEvents[0], "when a cell in first column is selected and argument is same as number of rows");
|
||||
|
||||
selection.removeAllRanges();
|
||||
editor.innerHTML =
|
||||
'<table><tr><td id="select">cell1-1</td><td>cell1-2</td></tr><tr><td>cell2-1</td><td>cell2-2</td></tr></table>';
|
||||
inputEvents = [];
|
||||
range = document.createRange();
|
||||
range.selectNode(document.getElementById("select"));
|
||||
selection.addRange(range);
|
||||
getTableEditor().deleteTableColumn(3);
|
||||
is(editor.innerHTML, "",
|
||||
"nsITableEditor.deleteTableColumn(3) should delete the <table> when argument is larger than actual number of columns");
|
||||
is(inputEvents.length, 1,
|
||||
'Only one "input" event should be fired when argument is larger than actual number of columns');
|
||||
checkInputEvent(inputEvents[0], "when argument is larger than actual number of columns");
|
||||
|
||||
selection.removeAllRanges();
|
||||
editor.innerHTML =
|
||||
'<table><tr><td>cell1-1</td><td id="select">cell1-2</td><td>cell1-3</td></tr><tr><td>cell2-1</td><td>cell2-2</td><td>cell2-3</td></tr></table>';
|
||||
inputEvents = [];
|
||||
range = document.createRange();
|
||||
range.selectNode(document.getElementById("select"));
|
||||
selection.addRange(range);
|
||||
getTableEditor().deleteTableColumn(2);
|
||||
is(editor.innerHTML, "<table><tbody><tr><td>cell1-1</td></tr><tr><td>cell2-1</td></tr></tbody></table>",
|
||||
"nsITableEditor.deleteTableColumn(2) should delete the second column containing selected cell and next column");
|
||||
is(inputEvents.length, 1,
|
||||
'Only one "input" event should be fired when a cell in second column and argument is same as the remaining columns');
|
||||
checkInputEvent(inputEvents[0], "when a cell in second column and argument is same as the remaining columns");
|
||||
|
||||
selection.removeAllRanges();
|
||||
editor.innerHTML =
|
||||
'<table><tr><td>cell1-1</td><td>cell1-2</td></tr><tr><td id="select">cell2-1</td><td>cell2-2</td></tr><tr><td>cell3-1</td><td>cell3-2</td></tr></table>';
|
||||
inputEvents = [];
|
||||
range = document.createRange();
|
||||
range.selectNode(document.getElementById("select"));
|
||||
selection.addRange(range);
|
||||
getTableEditor().deleteTableColumn(3);
|
||||
is(editor.innerHTML, "",
|
||||
"nsITableEditor.deleteTableColumn(3) should delete the <table> since the argument equals actual number of columns");
|
||||
is(inputEvents.length, 1,
|
||||
'Only one "input" event should be fired when a cell in first column and argument is larger than the remaining columns');
|
||||
checkInputEvent(inputEvents[0], "when a cell in first column and argument is larger than the remaining columns");
|
||||
|
||||
// Similar to selected a cell, when selection is in a cell, the cell should
|
||||
// treated as selected.
|
||||
selection.removeAllRanges();
|
||||
editor.innerHTML =
|
||||
'<table><tr><td id="select">cell1-1</td><td>cell1-2</td></tr><tr><td>cell2-1</td><td>cell2-2</td></tr></table>';
|
||||
inputEvents = [];
|
||||
editor.scrollTop; // Needs to flush pending reflow since we need layout information in this case.
|
||||
range = document.createRange();
|
||||
range.selectNode(document.getElementById("select").firstChild);
|
||||
|
@ -108,10 +156,14 @@ SimpleTest.waitForFocus(function() {
|
|||
getTableEditor().deleteTableColumn(1);
|
||||
is(editor.innerHTML, "<table><tbody><tr><td>cell1-2</td></tr><tr><td>cell2-2</td></tr></tbody></table>",
|
||||
"nsITableEditor.deleteTableColumn(1) should delete the first column when a cell in the first column contains selection range");
|
||||
is(inputEvents.length, 1,
|
||||
'Only one "input" event should be fired when a cell in the first column contains selection range');
|
||||
checkInputEvent(inputEvents[0], "when a cell in the first column contains selection range");
|
||||
|
||||
selection.removeAllRanges();
|
||||
editor.innerHTML =
|
||||
'<table><tr><td>cell1-1</td><td>cell1-2</td></tr><tr><td>cell2-1</td><td id="select">cell2-2</td></tr></table>';
|
||||
inputEvents = [];
|
||||
editor.scrollTop; // Needs to flush pending reflow since we need layout information in this case.
|
||||
range = document.createRange();
|
||||
range.selectNode(document.getElementById("select").firstChild);
|
||||
|
@ -119,10 +171,14 @@ SimpleTest.waitForFocus(function() {
|
|||
getTableEditor().deleteTableColumn(1);
|
||||
is(editor.innerHTML, "<table><tbody><tr><td>cell1-1</td></tr><tr><td>cell2-1</td></tr></tbody></table>",
|
||||
"nsITableEditor.deleteTableColumn(1) should delete the second column when a cell in the second column contains selection range");
|
||||
is(inputEvents.length, 1,
|
||||
'Only one "input" event should be fired when a cell in the second column contains selection range');
|
||||
checkInputEvent(inputEvents[0], "when a cell in the second column contains selection range");
|
||||
|
||||
selection.removeAllRanges();
|
||||
editor.innerHTML =
|
||||
'<table><tr><td id="select">cell1-1</td><td>cell1-2</td></tr><tr><td>cell2-1</td><td>cell2-2</td></tr></table>';
|
||||
inputEvents = [];
|
||||
editor.scrollTop; // Needs to flush pending reflow since we need layout information in this case.
|
||||
range = document.createRange();
|
||||
range.selectNode(document.getElementById("select").firstChild);
|
||||
|
@ -130,10 +186,14 @@ SimpleTest.waitForFocus(function() {
|
|||
getTableEditor().deleteTableColumn(2);
|
||||
is(editor.innerHTML, "",
|
||||
"nsITableEditor.deleteTableColumn(2) should delete the <table> since there is only 2 columns");
|
||||
is(inputEvents.length, 1,
|
||||
'Only one "input" event should be fired when all text in a cell in first column is selected and argument includes next row');
|
||||
checkInputEvent(inputEvents[0], "when all text in a cell in first column is selected and argument includes next row");
|
||||
|
||||
selection.removeAllRanges();
|
||||
editor.innerHTML =
|
||||
'<table><tr><td id="select">cell1-1</td><td>cell1-2</td></tr><tr><td>cell2-1</td><td>cell2-2</td></tr></table>';
|
||||
inputEvents = [];
|
||||
editor.scrollTop; // Needs to flush pending reflow since we need layout information in this case.
|
||||
range = document.createRange();
|
||||
range.selectNode(document.getElementById("select").firstChild);
|
||||
|
@ -141,10 +201,14 @@ SimpleTest.waitForFocus(function() {
|
|||
getTableEditor().deleteTableColumn(3);
|
||||
is(editor.innerHTML, "",
|
||||
"nsITableEditor.deleteTableColumn(3) should delete the <table> when argument is larger than actual number of columns");
|
||||
is(inputEvents.length, 1,
|
||||
'Only one "input" event should be fired when all text in a cell in first column is selected and argument is same as number of all rows');
|
||||
checkInputEvent(inputEvents[0], "when all text in a cell in first column is selected and argument is same as number of all rows");
|
||||
|
||||
selection.removeAllRanges();
|
||||
editor.innerHTML =
|
||||
'<table><tr><td>cell1-1</td><td id="select">cell1-2</td><td>cell1-3</td></tr><tr><td>cell2-1</td><td>cell2-2</td><td>cell2-3</td></tr></table>';
|
||||
inputEvents = [];
|
||||
editor.scrollTop; // Needs to flush pending reflow since we need layout information in this case.
|
||||
range = document.createRange();
|
||||
range.selectNode(document.getElementById("select").firstChild);
|
||||
|
@ -152,10 +216,14 @@ SimpleTest.waitForFocus(function() {
|
|||
getTableEditor().deleteTableColumn(2);
|
||||
is(editor.innerHTML, "<table><tbody><tr><td>cell1-1</td></tr><tr><td>cell2-1</td></tr></tbody></table>",
|
||||
"nsITableEditor.deleteTableColumn(2) should delete the second column containing a cell containing selection range and next column");
|
||||
is(inputEvents.length, 1,
|
||||
'Only one "input" event should be fired when all text in a cell is selected and argument is same than renaming number of columns');
|
||||
checkInputEvent(inputEvents[0], "when all text in a cell is selected and argument is same than renaming number of columns");
|
||||
|
||||
selection.removeAllRanges();
|
||||
editor.innerHTML =
|
||||
'<table><tr><td>cell1-1</td><td>cell1-2</td></tr><tr><td id="select">cell2-1</td><td>cell2-2</td></tr><tr><td>cell3-1</td><td>cell3-2</td></tr></table>';
|
||||
inputEvents = [];
|
||||
editor.scrollTop; // Needs to flush pending reflow since we need layout information in this case.
|
||||
range = document.createRange();
|
||||
range.selectNode(document.getElementById("select").firstChild);
|
||||
|
@ -163,12 +231,16 @@ SimpleTest.waitForFocus(function() {
|
|||
getTableEditor().deleteTableColumn(3);
|
||||
is(editor.innerHTML, "",
|
||||
"nsITableEditor.deleteTableColumn(3) should delete the <table> since the argument equals actual number of columns");
|
||||
is(inputEvents.length, 1,
|
||||
'Only one "input" event should be fired when all text in a cell in the first column and argument is larger than renaming number of columns');
|
||||
checkInputEvent(inputEvents[0], "when all text in a cell in the first column and argument is larger than renaming number of columns");
|
||||
|
||||
// The argument should be ignored when 2 or more cells are selected.
|
||||
// XXX Different from deleteTableRow(), this removes the <table> completely.
|
||||
selection.removeAllRanges();
|
||||
editor.innerHTML =
|
||||
'<table><tr><td id="select1">cell1-1</td><td>cell1-2</td></tr><tr><td>cell2-1</td><td id="select2">cell2-2</td></tr></table>';
|
||||
inputEvents = [];
|
||||
range = document.createRange();
|
||||
range.selectNode(document.getElementById("select1"));
|
||||
selection.addRange(range);
|
||||
|
@ -178,10 +250,14 @@ SimpleTest.waitForFocus(function() {
|
|||
getTableEditor().deleteTableColumn(1);
|
||||
is(editor.innerHTML, "",
|
||||
"nsITableEditor.deleteTableColumn(1) should delete the <table> when both columns have selected cell");
|
||||
is(inputEvents.length, 1,
|
||||
'Only one "input" event should be fired when both columns have selected cell');
|
||||
checkInputEvent(inputEvents[0], "when both columns have selected cell");
|
||||
|
||||
selection.removeAllRanges();
|
||||
editor.innerHTML =
|
||||
'<table><tr><td id="select1">cell1-1</td><td>cell1-2</td></tr><tr><td id="select2">cell2-1</td><td>cell2-2</td></tr></table>';
|
||||
inputEvents = [];
|
||||
range = document.createRange();
|
||||
range.selectNode(document.getElementById("select1"));
|
||||
selection.addRange(range);
|
||||
|
@ -191,10 +267,14 @@ SimpleTest.waitForFocus(function() {
|
|||
getTableEditor().deleteTableColumn(2);
|
||||
is(editor.innerHTML, "",
|
||||
"nsITableEditor.deleteTableColumn(2) should delete the <table> since 2 is number of columns of the <table>");
|
||||
is(inputEvents.length, 1,
|
||||
'Only one "input" event should be fired when cells in every column are selected #2');
|
||||
checkInputEvent(inputEvents[0], "when cells in every column are selected #2");
|
||||
|
||||
selection.removeAllRanges();
|
||||
editor.innerHTML =
|
||||
'<table><tr><td id="select1">cell1-1</td><td>cell1-2</td></tr><tr><td id="select2">cell2-1</td><td>cell2-2</td></tr></table>';
|
||||
inputEvents = [];
|
||||
range = document.createRange();
|
||||
range.selectNode(document.getElementById("select1"));
|
||||
selection.addRange(range);
|
||||
|
@ -204,10 +284,14 @@ SimpleTest.waitForFocus(function() {
|
|||
getTableEditor().deleteTableColumn(2);
|
||||
is(editor.innerHTML, "",
|
||||
"nsITableEditor.deleteTableColumn(2) should delete the <table> since 2 is number of columns of the <table>");
|
||||
is(inputEvents.length, 1,
|
||||
'Only one "input" event should be fired when 2 cells in same column are selected');
|
||||
checkInputEvent(inputEvents[0], "when 2 cells in same column are selected");
|
||||
|
||||
selection.removeAllRanges();
|
||||
editor.innerHTML =
|
||||
'<table><tr><td id="select1">cell1-1</td><td id="select2">cell1-2</td><td>cell1-3</td></tr><tr><td>cell2-1</td><td>cell2-2</td><td>cell2-3</td></tr></table>';
|
||||
inputEvents = [];
|
||||
range = document.createRange();
|
||||
range.selectNode(document.getElementById("select1"));
|
||||
selection.addRange(range);
|
||||
|
@ -217,10 +301,14 @@ SimpleTest.waitForFocus(function() {
|
|||
getTableEditor().deleteTableColumn(1);
|
||||
is(editor.innerHTML, "<table><tbody><tr><td>cell1-3</td></tr><tr><td>cell2-3</td></tr></tbody></table>",
|
||||
"nsITableEditor.deleteTableColumn(1) should delete first 2 columns because cells in the both columns are selected");
|
||||
is(inputEvents.length, 1,
|
||||
'Only one "input" event should be fired when 2 cell elements in different columns are selected #1');
|
||||
checkInputEvent(inputEvents[0], "when 2 cell elements in different columns are selected #1");
|
||||
|
||||
selection.removeAllRanges();
|
||||
editor.innerHTML =
|
||||
'<table><tr><td id="select1">cell1-1</td><td>cell1-2</td><td id="select2">cell1-3</td></tr><tr><td>cell2-1</td><td>cell2-2</td><td>cell2-3</td></tr></table>';
|
||||
inputEvents = [];
|
||||
range = document.createRange();
|
||||
range.selectNode(document.getElementById("select1"));
|
||||
selection.addRange(range);
|
||||
|
@ -230,46 +318,65 @@ SimpleTest.waitForFocus(function() {
|
|||
getTableEditor().deleteTableColumn(1);
|
||||
is(editor.innerHTML, "<table><tbody><tr><td>cell1-2</td></tr><tr><td>cell2-2</td></tr></tbody></table>",
|
||||
"nsITableEditor.deleteTableColumn(1) should delete the first and the last columns because cells in the both columns are selected");
|
||||
is(inputEvents.length, 1,
|
||||
'Only one "input" event should be fired when 2 cell elements in different columns are selected #2');
|
||||
checkInputEvent(inputEvents[0], "when 2 cell elements in different columns are selected #2");
|
||||
|
||||
selection.removeAllRanges();
|
||||
editor.innerHTML =
|
||||
'<table><tr><td id="select" colspan="2">cell1-1</td><td>cell1-3</td></tr><tr><td>cell2-1</td><td>cell2-2</td><td>cell2-3</td></tr></table>';
|
||||
inputEvents = [];
|
||||
range = document.createRange();
|
||||
range.selectNode(document.getElementById("select"));
|
||||
selection.addRange(range);
|
||||
getTableEditor().deleteTableColumn(1);
|
||||
is(editor.innerHTML, '<table><tbody><tr><td id="select" colspan="1"><br></td><td>cell1-3</td></tr><tr><td>cell2-2</td><td>cell2-3</td></tr></tbody></table>',
|
||||
"nsITableEditor.deleteTableColumn(1) with a selected cell is colspan=\"2\" should delete the first column and add empty cell to the second column");
|
||||
is(inputEvents.length, 1,
|
||||
'Only one "input" event should be fired when a cell is selected and its colspan is 2');
|
||||
checkInputEvent(inputEvents[0], "when a cell is selected and its colspan is 2");
|
||||
|
||||
selection.removeAllRanges();
|
||||
editor.innerHTML =
|
||||
'<table><tr><td id="select" colspan="3">cell1-1</td></tr><tr><td>cell2-1</td><td>cell2-2</td><td>cell2-3</td></tr></table>';
|
||||
inputEvents = [];
|
||||
range = document.createRange();
|
||||
range.selectNode(document.getElementById("select"));
|
||||
selection.addRange(range);
|
||||
getTableEditor().deleteTableColumn(1);
|
||||
is(editor.innerHTML, '<table><tbody><tr><td id="select" colspan="2"><br></td></tr><tr><td>cell2-2</td><td>cell2-3</td></tr></tbody></table>',
|
||||
"nsITableEditor.deleteTableColumn(1) with a selected cell is colspan=\"3\" should delete the first column and add empty cell whose colspan is 2 to the second column");
|
||||
is(inputEvents.length, 1,
|
||||
'Only one "input" event should be fired when a cell is selected and its colspan is 3');
|
||||
checkInputEvent(inputEvents[0], "when a cell is selected and its colspan is 3");
|
||||
|
||||
selection.removeAllRanges();
|
||||
editor.innerHTML =
|
||||
'<table><tr><td colspan="3">cell1-1</td></tr><tr><td>cell2-1</td><td id="select">cell2-2</td><td>cell2-3</td></tr></table>';
|
||||
inputEvents = [];
|
||||
range = document.createRange();
|
||||
range.selectNode(document.getElementById("select"));
|
||||
selection.addRange(range);
|
||||
getTableEditor().deleteTableColumn(1);
|
||||
is(editor.innerHTML, '<table><tbody><tr><td colspan="2">cell1-1</td></tr><tr><td>cell2-1</td><td>cell2-3</td></tr></tbody></table>',
|
||||
"nsITableEditor.deleteTableColumn(1) with selected cell in the second column should delete the second column and the colspan in the first row should be adjusted");
|
||||
is(inputEvents.length, 1,
|
||||
'Only one "input" event should be fired when a cell in 2nd column is only cell defined by the column #1');
|
||||
checkInputEvent(inputEvents[0], "when a cell in 2nd column is only cell defined by the column #1");
|
||||
|
||||
selection.removeAllRanges();
|
||||
editor.innerHTML =
|
||||
'<table><tr><td colspan="2">cell1-1</td><td>cell1-3</td></tr><tr><td>cell2-1</td><td id="select">cell2-2</td><td>cell2-3</td></tr></table>';
|
||||
inputEvents = [];
|
||||
range = document.createRange();
|
||||
range.selectNode(document.getElementById("select"));
|
||||
selection.addRange(range);
|
||||
getTableEditor().deleteTableColumn(1);
|
||||
is(editor.innerHTML, '<table><tbody><tr><td colspan="1">cell1-1</td><td>cell1-3</td></tr><tr><td>cell2-1</td><td>cell2-3</td></tr></tbody></table>',
|
||||
"nsITableEditor.deleteTableColumn(1) with selected cell in the second column should delete the second column and the colspan should be adjusted");
|
||||
is(inputEvents.length, 1,
|
||||
'Only one "input" event should be fired when a cell in 2nd column is only cell defined by the column #2');
|
||||
checkInputEvent(inputEvents[0], "when a cell in 2nd column is only cell defined by the column #2");
|
||||
|
||||
SimpleTest.finish();
|
||||
});
|
||||
|
|
|
@ -26,6 +26,8 @@ SimpleTest.waitForFocus(function() {
|
|||
`"input" event should be never cancelable ${aDescription}`);
|
||||
is(aEvent.bubbles, true,
|
||||
`"input" event should always bubble ${aDescription}`);
|
||||
is(aEvent.inputType, "deleteContent",
|
||||
`inputType should be "deleteContent" ${aDescription}`);
|
||||
}
|
||||
|
||||
let inputEvents = [];
|
||||
|
|
|
@ -26,6 +26,8 @@ SimpleTest.waitForFocus(function() {
|
|||
`"input" event should be never cancelable ${aDescription}`);
|
||||
is(aEvent.bubbles, true,
|
||||
`"input" event should always bubble ${aDescription}`);
|
||||
is(aEvent.inputType, "",
|
||||
`inputType should be empty string ${aDescription}`);
|
||||
}
|
||||
|
||||
let inputEvents = [];
|
||||
|
|
|
@ -26,6 +26,8 @@ SimpleTest.waitForFocus(function() {
|
|||
`"input" event should be never cancelable ${aDescription}`);
|
||||
is(aEvent.bubbles, true,
|
||||
`"input" event should always bubble ${aDescription}`);
|
||||
is(aEvent.inputType, "",
|
||||
`inputType should be empty string ${aDescription}`);
|
||||
}
|
||||
|
||||
let inputEvents = [];
|
||||
|
|
|
@ -26,6 +26,8 @@ SimpleTest.waitForFocus(function() {
|
|||
`"input" event should be never cancelable ${aDescription}`);
|
||||
is(aEvent.bubbles, true,
|
||||
`"input" event should always bubble ${aDescription}`);
|
||||
is(aEvent.inputType, "",
|
||||
`inputType should be empty string ${aDescription}`);
|
||||
}
|
||||
|
||||
let inputEvents = [];
|
||||
|
|
|
@ -97,6 +97,8 @@ SimpleTest.waitForFocus(async function() {
|
|||
'"input" event should be never cancelable');
|
||||
is(aEvent.bubbles, true,
|
||||
'"input" event should always bubble');
|
||||
is(aEvent.inputType, "",
|
||||
`inputType should be empty string when an element is resized`);
|
||||
}
|
||||
|
||||
content.addEventListener("input", onInput);
|
||||
|
|
|
@ -21,13 +21,15 @@ SimpleTest.waitForFocus(() => {
|
|||
|
||||
textarea.focus();
|
||||
|
||||
function checkInputEvent(aEvent, aDescription) {
|
||||
function checkInputEvent(aEvent, aInputType, aDescription) {
|
||||
ok(aEvent instanceof InputEvent,
|
||||
`"input" event should be dispatched with InputEvent interface ${aDescription}`);
|
||||
is(aEvent.cancelable, false,
|
||||
`"input" event should be never cancelable ${aDescription}`);
|
||||
is(aEvent.bubbles, true,
|
||||
`"input" event should always bubble ${aDescription}`);
|
||||
is(aEvent.inputType, aInputType,
|
||||
`inputType should be "${aInputType}" ${aDescription}`);
|
||||
}
|
||||
|
||||
let inputEvents = [];
|
||||
|
@ -52,7 +54,7 @@ SimpleTest.waitForFocus(() => {
|
|||
"'abx' should be replaced with 'aux'");
|
||||
is(inputEvents.length, 1,
|
||||
'Only one "input" event should be fired when replacing a word with spellchecker');
|
||||
checkInputEvent(inputEvents[0], "when replacing a word with spellchecker");
|
||||
checkInputEvent(inputEvents[0], "insertReplacementText", "when replacing a word with spellchecker");
|
||||
|
||||
inputEvents = [];
|
||||
synthesizeKey("z", { accelKey: true });
|
||||
|
@ -60,7 +62,7 @@ SimpleTest.waitForFocus(() => {
|
|||
"'abx' should be restored by undo");
|
||||
is(inputEvents.length, 1,
|
||||
'Only one "input" event should be fired when undoing the replacing word');
|
||||
checkInputEvent(inputEvents[0], "when undoing the replacing word");
|
||||
checkInputEvent(inputEvents[0], "historyUndo", "when undoing the replacing word");
|
||||
|
||||
inputEvents = [];
|
||||
synthesizeKey("z", { accelKey: true, shiftKey: true });
|
||||
|
@ -68,7 +70,7 @@ SimpleTest.waitForFocus(() => {
|
|||
"'aux' should be restored by redo");
|
||||
is(inputEvents.length, 1,
|
||||
'Only one "input" event should be fired when redoing the replacing word');
|
||||
checkInputEvent(inputEvents[0], "when redoing the replacing word");
|
||||
checkInputEvent(inputEvents[0], "historyRedo", "when redoing the replacing word");
|
||||
|
||||
textarea.removeEventListener("input", onInput);
|
||||
|
||||
|
|
|
@ -29,13 +29,15 @@ SimpleTest.waitForFocus(function() {
|
|||
document.getElementById("textarea"),
|
||||
];
|
||||
for (let editableElement of editableElements) {
|
||||
function checkInputEvent(aEvent, aDescription) {
|
||||
function checkInputEvent(aEvent, aInputType, aDescription) {
|
||||
ok(aEvent instanceof InputEvent,
|
||||
`"input" event should be dispatched with InputEvent interface ${aDescription}`);
|
||||
is(aEvent.cancelable, false,
|
||||
`"input" event should be never cancelable ${aDescription}`);
|
||||
is(aEvent.bubbles, true,
|
||||
`"input" event should always bubble ${aDescription}`);
|
||||
is(aEvent.inputType, aInputType,
|
||||
`inputType should be "${aInputType}" ${aDescription}`);
|
||||
}
|
||||
|
||||
let inputEvents = [];
|
||||
|
@ -50,13 +52,13 @@ SimpleTest.waitForFocus(function() {
|
|||
synthesizeKey("a");
|
||||
is(inputEvents.length, 1,
|
||||
`Only one "input" event should be fired when inserting "a" with key on <${editableElement.tagName.toLowerCase()}> element`);
|
||||
checkInputEvent(inputEvents[0], `when inserting "a" with key on <${editableElement.tagName.toLowerCase()}> element`);
|
||||
checkInputEvent(inputEvents[0], "insertText", `when inserting "a" with key on <${editableElement.tagName.toLowerCase()}> element`);
|
||||
|
||||
inputEvents = [];
|
||||
synthesizeKey("c");
|
||||
is(inputEvents.length, 1,
|
||||
`Only one "input" event should be fired when inserting "c" with key on <${editableElement.tagName.toLowerCase()}> element`);
|
||||
checkInputEvent(inputEvents[0], `when inserting "c" with key on <${editableElement.tagName.toLowerCase()}> element`);
|
||||
checkInputEvent(inputEvents[0], "insertText", `when inserting "c" with key on <${editableElement.tagName.toLowerCase()}> element`);
|
||||
|
||||
inputEvents = [];
|
||||
synthesizeKey("KEY_ArrowLeft");
|
||||
|
@ -67,7 +69,7 @@ SimpleTest.waitForFocus(function() {
|
|||
synthesizeKey("b");
|
||||
is(inputEvents.length, 1,
|
||||
`Only one "input" event should be fired when inserting "b" with key on <${editableElement.tagName.toLowerCase()}> element`);
|
||||
checkInputEvent(inputEvents[0], `when inserting "b" with key on <${editableElement.tagName.toLowerCase()}> element`);
|
||||
checkInputEvent(inputEvents[0], "insertText", `when inserting "b" with key on <${editableElement.tagName.toLowerCase()}> element`);
|
||||
|
||||
let editor = SpecialPowers.wrap(editableElement).editor;
|
||||
let transactionManager = editor.transactionManager;
|
||||
|
@ -91,7 +93,7 @@ SimpleTest.waitForFocus(function() {
|
|||
synthesizeKey("a");
|
||||
is(inputEvents.length, 1,
|
||||
`Only one "input" event should be fired when inserting "a" with key again on <${editableElement.tagName.toLowerCase()}> element`);
|
||||
checkInputEvent(inputEvents[0], `when inserting "a" with key again on <${editableElement.tagName.toLowerCase()}> element`);
|
||||
checkInputEvent(inputEvents[0], "insertText", `when inserting "a" with key again on <${editableElement.tagName.toLowerCase()}> element`);
|
||||
|
||||
inputEvents = [];
|
||||
synthesizeKey("z", { accelKey: true });
|
||||
|
@ -99,7 +101,7 @@ SimpleTest.waitForFocus(function() {
|
|||
editableElement.tagName + ": undo should work after setting value");
|
||||
is(inputEvents.length, 1,
|
||||
`Only one "input" event should be fired when undoing on <${editableElement.tagName.toLowerCase()}> element`);
|
||||
checkInputEvent(inputEvents[0], `when undoing on <${editableElement.tagName.toLowerCase()}> element`);
|
||||
checkInputEvent(inputEvents[0], "historyUndo", `when undoing on <${editableElement.tagName.toLowerCase()}> element`);
|
||||
|
||||
// Disable undo/redo.
|
||||
editor.enableUndo(0);
|
||||
|
|
|
@ -115,6 +115,8 @@ using namespace std;
|
|||
using base::ProcessId;
|
||||
using base::Thread;
|
||||
|
||||
using mozilla::Telemetry::LABELS_CONTENT_FRAME_TIME_REASON;
|
||||
|
||||
/// Equivalent to asserting CompositorThreadHolder::IsInCompositorThread with
|
||||
/// the addition that it doesn't assert if the compositor thread holder is
|
||||
/// already gone during late shutdown.
|
||||
|
@ -2412,5 +2414,130 @@ mozilla::ipc::IPCResult CompositorBridgeParent::RecvAllPluginsCaptured() {
|
|||
#endif
|
||||
}
|
||||
|
||||
int32_t RecordContentFrameTime(
|
||||
const VsyncId& aTxnId, const TimeStamp& aVsyncStart,
|
||||
const TimeStamp& aTxnStart, const VsyncId& aCompositeId,
|
||||
const TimeStamp& aCompositeEnd, const TimeDuration& aFullPaintTime,
|
||||
const TimeDuration& aVsyncRate, bool aContainsSVGGroup,
|
||||
bool aRecordUploadStats, wr::RendererStats* aStats /* = nullptr */) {
|
||||
double latencyMs = (aCompositeEnd - aTxnStart).ToMilliseconds();
|
||||
double latencyNorm = latencyMs / aVsyncRate.ToMilliseconds();
|
||||
int32_t fracLatencyNorm = lround(latencyNorm * 100.0);
|
||||
|
||||
#ifdef MOZ_GECKO_PROFILER
|
||||
if (profiler_is_active()) {
|
||||
class ContentFramePayload : public ProfilerMarkerPayload {
|
||||
public:
|
||||
ContentFramePayload(const mozilla::TimeStamp& aStartTime,
|
||||
const mozilla::TimeStamp& aEndTime)
|
||||
: ProfilerMarkerPayload(aStartTime, aEndTime) {}
|
||||
virtual void StreamPayload(SpliceableJSONWriter& aWriter,
|
||||
const TimeStamp& aProcessStartTime,
|
||||
UniqueStacks& aUniqueStacks) override {
|
||||
StreamCommonProps("CONTENT_FRAME_TIME", aWriter, aProcessStartTime,
|
||||
aUniqueStacks);
|
||||
}
|
||||
};
|
||||
profiler_add_marker_for_thread(
|
||||
profiler_current_thread_id(), "CONTENT_FRAME_TIME",
|
||||
MakeUnique<ContentFramePayload>(aTxnStart, aCompositeEnd));
|
||||
}
|
||||
#endif
|
||||
|
||||
Telemetry::Accumulate(Telemetry::CONTENT_FRAME_TIME, fracLatencyNorm);
|
||||
|
||||
if (!(aTxnId == VsyncId()) && aVsyncStart) {
|
||||
latencyMs = (aCompositeEnd - aVsyncStart).ToMilliseconds();
|
||||
latencyNorm = latencyMs / aVsyncRate.ToMilliseconds();
|
||||
fracLatencyNorm = lround(latencyNorm * 100.0);
|
||||
int32_t result = fracLatencyNorm;
|
||||
Telemetry::Accumulate(Telemetry::CONTENT_FRAME_TIME_VSYNC, fracLatencyNorm);
|
||||
|
||||
if (aContainsSVGGroup) {
|
||||
Telemetry::Accumulate(Telemetry::CONTENT_FRAME_TIME_WITH_SVG,
|
||||
fracLatencyNorm);
|
||||
}
|
||||
|
||||
// Record CONTENT_FRAME_TIME_REASON.
|
||||
//
|
||||
// Note that deseralizing a layers update (RecvUpdate) can delay the receipt
|
||||
// of the composite vsync message
|
||||
// (CompositorBridgeParent::CompositeToTarget), since they're using the same
|
||||
// thread. This can mean that compositing might start significantly late,
|
||||
// but this code will still detect it as having successfully started on the
|
||||
// right vsync (which is somewhat correct). We'd now have reduced time left
|
||||
// in the vsync interval to finish compositing, so the chances of a missed
|
||||
// frame increases. This is effectively including the RecvUpdate work as
|
||||
// part of the 'compositing' phase for this metric, but it isn't included in
|
||||
// COMPOSITE_TIME, and *is* included in CONTENT_FULL_PAINT_TIME.
|
||||
//
|
||||
// Also of note is that when the root WebRenderBridgeParent decides to
|
||||
// skip a composite (due to the Renderer being busy), that won't notify
|
||||
// child WebRenderBridgeParents. That failure will show up as the
|
||||
// composite starting late (since it did), but it's really a fault of a
|
||||
// slow composite on the previous frame, not a slow
|
||||
// CONTENT_FULL_PAINT_TIME. It would be nice to have a separate bucket for
|
||||
// this category (scene was ready on the next vsync, but we chose not to
|
||||
// composite), but I can't find a way to locate the right child
|
||||
// WebRenderBridgeParents from the root. WebRender notifies us of the
|
||||
// child pipelines contained within a render, after it finishes, but I
|
||||
// can't see how to query what child pipeline would have been rendered,
|
||||
// when we choose to not do it.
|
||||
if (fracLatencyNorm < 200) {
|
||||
// Success
|
||||
Telemetry::AccumulateCategorical(
|
||||
LABELS_CONTENT_FRAME_TIME_REASON::OnTime);
|
||||
} else {
|
||||
if (aCompositeId == VsyncId() || aTxnId >= aCompositeId) {
|
||||
// Vsync ids are nonsensical, possibly something got trigged from
|
||||
// outside vsync?
|
||||
Telemetry::AccumulateCategorical(
|
||||
LABELS_CONTENT_FRAME_TIME_REASON::NoVsync);
|
||||
} else if (aCompositeId - aTxnId > 1) {
|
||||
// Composite started late (and maybe took too long as well)
|
||||
if (aFullPaintTime >= TimeDuration::FromMilliseconds(20)) {
|
||||
Telemetry::AccumulateCategorical(
|
||||
LABELS_CONTENT_FRAME_TIME_REASON::MissedCompositeLong);
|
||||
} else if (aFullPaintTime >= TimeDuration::FromMilliseconds(10)) {
|
||||
Telemetry::AccumulateCategorical(
|
||||
LABELS_CONTENT_FRAME_TIME_REASON::MissedCompositeMid);
|
||||
} else if (aFullPaintTime >= TimeDuration::FromMilliseconds(5)) {
|
||||
Telemetry::AccumulateCategorical(
|
||||
LABELS_CONTENT_FRAME_TIME_REASON::MissedCompositeLow);
|
||||
} else {
|
||||
Telemetry::AccumulateCategorical(
|
||||
LABELS_CONTENT_FRAME_TIME_REASON::MissedComposite);
|
||||
}
|
||||
} else {
|
||||
// Composite started on time, but must have taken too long.
|
||||
Telemetry::AccumulateCategorical(
|
||||
LABELS_CONTENT_FRAME_TIME_REASON::SlowComposite);
|
||||
}
|
||||
}
|
||||
|
||||
if (aRecordUploadStats) {
|
||||
if (aStats) {
|
||||
latencyMs -= (double(aStats->resource_upload_time) / 1000000.0);
|
||||
latencyNorm = latencyMs / aVsyncRate.ToMilliseconds();
|
||||
fracLatencyNorm = lround(latencyNorm * 100.0);
|
||||
}
|
||||
Telemetry::Accumulate(
|
||||
Telemetry::CONTENT_FRAME_TIME_WITHOUT_RESOURCE_UPLOAD,
|
||||
fracLatencyNorm);
|
||||
|
||||
if (aStats) {
|
||||
latencyMs -= (double(aStats->gpu_cache_upload_time) / 1000000.0);
|
||||
latencyNorm = latencyMs / aVsyncRate.ToMilliseconds();
|
||||
fracLatencyNorm = lround(latencyNorm * 100.0);
|
||||
}
|
||||
Telemetry::Accumulate(Telemetry::CONTENT_FRAME_TIME_WITHOUT_UPLOAD,
|
||||
fracLatencyNorm);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
} // namespace layers
|
||||
} // namespace mozilla
|
||||
|
|
|
@ -704,6 +704,13 @@ class CompositorBridgeParent final : public CompositorBridgeParentBase,
|
|||
DISALLOW_EVIL_CONSTRUCTORS(CompositorBridgeParent);
|
||||
};
|
||||
|
||||
int32_t RecordContentFrameTime(
|
||||
const VsyncId& aTxnId, const TimeStamp& aVsyncStart,
|
||||
const TimeStamp& aTxnStart, const VsyncId& aCompositeId,
|
||||
const TimeStamp& aCompositeEnd, const TimeDuration& aFullPaintTime,
|
||||
const TimeDuration& aVsyncRate, bool aContainsSVGGroup,
|
||||
bool aRecordUploadStats, wr::RendererStats* aStats = nullptr);
|
||||
|
||||
} // namespace layers
|
||||
} // namespace mozilla
|
||||
|
||||
|
|
|
@ -391,7 +391,7 @@ void CrossProcessCompositorBridgeParent::ShadowLayersUpdated(
|
|||
|
||||
aLayerTree->SetPendingTransactionId(
|
||||
aInfo.id(), aInfo.vsyncId(), aInfo.vsyncStart(), aInfo.refreshStart(),
|
||||
aInfo.transactionStart(), aInfo.url(), aInfo.fwdTime());
|
||||
aInfo.transactionStart(), endTime, aInfo.url(), aInfo.fwdTime());
|
||||
}
|
||||
|
||||
void CrossProcessCompositorBridgeParent::DidCompositeLocked(
|
||||
|
|
|
@ -887,52 +887,9 @@ bool LayerTransactionParent::IsSameProcess() const {
|
|||
TransactionId LayerTransactionParent::FlushTransactionId(
|
||||
const VsyncId& aId, TimeStamp& aCompositeEnd) {
|
||||
if (mId.IsValid() && mPendingTransaction.IsValid() && !mVsyncRate.IsZero()) {
|
||||
double latencyMs = (aCompositeEnd - mTxnStartTime).ToMilliseconds();
|
||||
double latencyNorm = latencyMs / mVsyncRate.ToMilliseconds();
|
||||
int32_t fracLatencyNorm = lround(latencyNorm * 100.0);
|
||||
Telemetry::Accumulate(Telemetry::CONTENT_FRAME_TIME, fracLatencyNorm);
|
||||
|
||||
if (!(mTxnVsyncId == VsyncId()) && mVsyncStartTime) {
|
||||
latencyMs = (aCompositeEnd - mVsyncStartTime).ToMilliseconds();
|
||||
latencyNorm = latencyMs / mVsyncRate.ToMilliseconds();
|
||||
fracLatencyNorm = lround(latencyNorm * 100.0);
|
||||
Telemetry::Accumulate(Telemetry::CONTENT_FRAME_TIME_VSYNC,
|
||||
fracLatencyNorm);
|
||||
|
||||
// Record CONTENT_FRAME_TIME_REASON. See
|
||||
// WebRenderBridgeParent::FlushTransactionIdsForEpoch for more details.
|
||||
//
|
||||
// Note that deseralizing a layers update (RecvUpdate) can delay the receipt
|
||||
// of the composite vsync message
|
||||
// (CompositorBridgeParent::CompositeToTarget), since they're using the same
|
||||
// thread. This can mean that compositing might start significantly late,
|
||||
// but this code will still detect it as having successfully started on the
|
||||
// right vsync (which is somewhat correct). We'd now have reduced time left
|
||||
// in the vsync interval to finish compositing, so the chances of a missed
|
||||
// frame increases. This is effectively including the RecvUpdate work as
|
||||
// part of the 'compositing' phase for this metric, but it isn't included in
|
||||
// COMPOSITE_TIME, and *is* included in CONTENT_FULL_PAINT_TIME.
|
||||
if (fracLatencyNorm < 200) {
|
||||
// Success
|
||||
Telemetry::AccumulateCategorical(
|
||||
LABELS_CONTENT_FRAME_TIME_REASON::OnTime);
|
||||
} else {
|
||||
if (mTxnVsyncId == VsyncId() || aId == VsyncId() || mTxnVsyncId >= aId) {
|
||||
// Vsync ids are nonsensical, possibly something got trigged from
|
||||
// outside vsync?
|
||||
Telemetry::AccumulateCategorical(
|
||||
LABELS_CONTENT_FRAME_TIME_REASON::NoVsync);
|
||||
} else if (aId - mTxnVsyncId > 1) {
|
||||
// Composite started late (and maybe took too long as well)
|
||||
Telemetry::AccumulateCategorical(
|
||||
LABELS_CONTENT_FRAME_TIME_REASON::MissedComposite);
|
||||
} else {
|
||||
// Composite start on time, but must have taken too long.
|
||||
Telemetry::AccumulateCategorical(
|
||||
LABELS_CONTENT_FRAME_TIME_REASON::SlowComposite);
|
||||
}
|
||||
}
|
||||
}
|
||||
RecordContentFrameTime(mTxnVsyncId, mVsyncStartTime, mTxnStartTime, aId,
|
||||
aCompositeEnd, mTxnEndTime - mTxnStartTime,
|
||||
mVsyncRate, false, false);
|
||||
}
|
||||
|
||||
#if defined(ENABLE_FRAME_LATENCY_LOG)
|
||||
|
|
|
@ -76,6 +76,7 @@ class LayerTransactionParent final : public PLayerTransactionParent,
|
|||
const TimeStamp& aVsyncStartTime,
|
||||
const TimeStamp& aRefreshStartTime,
|
||||
const TimeStamp& aTxnStartTime,
|
||||
const TimeStamp& aTxnEndTime,
|
||||
const nsCString& aURL,
|
||||
const TimeStamp& aFwdTime) {
|
||||
mPendingTransaction = aId;
|
||||
|
@ -83,6 +84,7 @@ class LayerTransactionParent final : public PLayerTransactionParent,
|
|||
mVsyncStartTime = aVsyncStartTime;
|
||||
mRefreshStartTime = aRefreshStartTime;
|
||||
mTxnStartTime = aTxnStartTime;
|
||||
mTxnEndTime = aTxnEndTime;
|
||||
mTxnURL = aURL;
|
||||
mFwdTime = aFwdTime;
|
||||
}
|
||||
|
@ -210,6 +212,7 @@ class LayerTransactionParent final : public PLayerTransactionParent,
|
|||
TimeStamp mVsyncStartTime;
|
||||
TimeStamp mRefreshStartTime;
|
||||
TimeStamp mTxnStartTime;
|
||||
TimeStamp mTxnEndTime;
|
||||
TimeStamp mFwdTime;
|
||||
nsCString mTxnURL;
|
||||
|
||||
|
|
|
@ -40,8 +40,6 @@
|
|||
#include "dwrite.h"
|
||||
#endif
|
||||
|
||||
using mozilla::Telemetry::LABELS_CONTENT_FRAME_TIME_REASON;
|
||||
|
||||
#ifdef MOZ_GECKO_PROFILER
|
||||
#include "ProfilerMarkerPayload.h"
|
||||
#endif
|
||||
|
@ -1916,129 +1914,28 @@ TransactionId WebRenderBridgeParent::FlushTransactionIdsForEpoch(
|
|||
|
||||
if (!IsRootWebRenderBridgeParent() && !mVsyncRate.IsZero() &&
|
||||
transactionId.mUseForTelemetry) {
|
||||
double latencyMs =
|
||||
(aEndTime - transactionId.mTxnStartTime).ToMilliseconds();
|
||||
double latencyNorm = latencyMs / mVsyncRate.ToMilliseconds();
|
||||
int32_t fracLatencyNorm = lround(latencyNorm * 100.0);
|
||||
auto fullPaintTime =
|
||||
transactionId.mSceneBuiltTime
|
||||
? transactionId.mSceneBuiltTime - transactionId.mTxnStartTime
|
||||
: TimeDuration::FromMilliseconds(0);
|
||||
|
||||
#ifdef MOZ_GECKO_PROFILER
|
||||
if (profiler_is_active()) {
|
||||
class ContentFramePayload : public ProfilerMarkerPayload {
|
||||
public:
|
||||
ContentFramePayload(const mozilla::TimeStamp& aStartTime,
|
||||
const mozilla::TimeStamp& aEndTime)
|
||||
: ProfilerMarkerPayload(aStartTime, aEndTime) {}
|
||||
virtual void StreamPayload(SpliceableJSONWriter& aWriter,
|
||||
const TimeStamp& aProcessStartTime,
|
||||
UniqueStacks& aUniqueStacks) override {
|
||||
StreamCommonProps("CONTENT_FRAME_TIME", aWriter, aProcessStartTime,
|
||||
aUniqueStacks);
|
||||
}
|
||||
};
|
||||
profiler_add_marker_for_thread(
|
||||
profiler_current_thread_id(), "CONTENT_FRAME_TIME",
|
||||
MakeUnique<ContentFramePayload>(transactionId.mTxnStartTime,
|
||||
aEndTime));
|
||||
}
|
||||
#endif
|
||||
|
||||
if (fracLatencyNorm > 200) {
|
||||
int32_t contentFrameTime = RecordContentFrameTime(
|
||||
transactionId.mVsyncId, transactionId.mVsyncStartTime,
|
||||
transactionId.mTxnStartTime, aCompositeStartId, aEndTime,
|
||||
fullPaintTime, mVsyncRate, transactionId.mContainsSVGGroup, true,
|
||||
aStats);
|
||||
if (contentFrameTime > 200) {
|
||||
aOutputStats->AppendElement(FrameStats(
|
||||
transactionId.mId, aCompositeStartTime, aRenderStartTime, aEndTime,
|
||||
fracLatencyNorm,
|
||||
contentFrameTime,
|
||||
aStats ? (double(aStats->resource_upload_time) / 1000000.0) : 0.0,
|
||||
aStats ? (double(aStats->gpu_cache_upload_time) / 1000000.0) : 0.0,
|
||||
transactionId.mTxnStartTime, transactionId.mRefreshStartTime,
|
||||
transactionId.mFwdTime, transactionId.mSceneBuiltTime,
|
||||
transactionId.mSkippedComposites, transactionId.mTxnURL));
|
||||
}
|
||||
|
||||
Telemetry::Accumulate(Telemetry::CONTENT_FRAME_TIME, fracLatencyNorm);
|
||||
if (fracLatencyNorm > 200) {
|
||||
wr::RenderThread::Get()->NotifySlowFrame(mApi->GetId());
|
||||
}
|
||||
if (transactionId.mContainsSVGGroup) {
|
||||
Telemetry::Accumulate(Telemetry::CONTENT_FRAME_TIME_WITH_SVG,
|
||||
fracLatencyNorm);
|
||||
}
|
||||
|
||||
if (aStats) {
|
||||
latencyMs -= (double(aStats->resource_upload_time) / 1000000.0);
|
||||
latencyNorm = latencyMs / mVsyncRate.ToMilliseconds();
|
||||
fracLatencyNorm = lround(latencyNorm * 100.0);
|
||||
}
|
||||
Telemetry::Accumulate(
|
||||
Telemetry::CONTENT_FRAME_TIME_WITHOUT_RESOURCE_UPLOAD,
|
||||
fracLatencyNorm);
|
||||
|
||||
if (aStats) {
|
||||
latencyMs -= (double(aStats->gpu_cache_upload_time) / 1000000.0);
|
||||
latencyNorm = latencyMs / mVsyncRate.ToMilliseconds();
|
||||
fracLatencyNorm = lround(latencyNorm * 100.0);
|
||||
}
|
||||
Telemetry::Accumulate(Telemetry::CONTENT_FRAME_TIME_WITHOUT_UPLOAD,
|
||||
fracLatencyNorm);
|
||||
|
||||
if (!(transactionId.mVsyncId == VsyncId()) &&
|
||||
transactionId.mVsyncStartTime) {
|
||||
latencyMs = (aEndTime - transactionId.mVsyncStartTime).ToMilliseconds();
|
||||
latencyNorm = latencyMs / mVsyncRate.ToMilliseconds();
|
||||
fracLatencyNorm = lround(latencyNorm * 100.0);
|
||||
Telemetry::Accumulate(Telemetry::CONTENT_FRAME_TIME_VSYNC,
|
||||
fracLatencyNorm);
|
||||
|
||||
// Record CONTENT_FRAME_TIME_REASON.
|
||||
//
|
||||
// Also of note is that when the root WebRenderBridgeParent decides to
|
||||
// skip a composite (due to the Renderer being busy), that won't notify
|
||||
// child WebRenderBridgeParents. That failure will show up as the
|
||||
// composite starting late (since it did), but it's really a fault of a
|
||||
// slow composite on the previous frame, not a slow
|
||||
// CONTENT_FULL_PAINT_TIME. It would be nice to have a separate bucket for
|
||||
// this category (scene was ready on the next vsync, but we chose not to
|
||||
// composite), but I can't find a way to locate the right child
|
||||
// WebRenderBridgeParents from the root. WebRender notifies us of the
|
||||
// child pipelines contained within a render, after it finishes, but I
|
||||
// can't see how to query what child pipeline would have been rendered,
|
||||
// when we choose to not do it.
|
||||
if (fracLatencyNorm < 200) {
|
||||
// Success
|
||||
Telemetry::AccumulateCategorical(
|
||||
LABELS_CONTENT_FRAME_TIME_REASON::OnTime);
|
||||
} else {
|
||||
if (transactionId.mVsyncId == VsyncId() ||
|
||||
aCompositeStartId == VsyncId() ||
|
||||
transactionId.mVsyncId >= aCompositeStartId) {
|
||||
// Vsync ids are nonsensical, possibly something got trigged from
|
||||
// outside vsync?
|
||||
Telemetry::AccumulateCategorical(
|
||||
LABELS_CONTENT_FRAME_TIME_REASON::NoVsync);
|
||||
} else if (aCompositeStartId - transactionId.mVsyncId > 1) {
|
||||
auto fullPaintTime =
|
||||
transactionId.mSceneBuiltTime
|
||||
? transactionId.mSceneBuiltTime - transactionId.mTxnStartTime
|
||||
: TimeDuration::FromMilliseconds(0);
|
||||
// Composite started late (and maybe took too long as well)
|
||||
if (fullPaintTime >= TimeDuration::FromMilliseconds(20)) {
|
||||
Telemetry::AccumulateCategorical(
|
||||
LABELS_CONTENT_FRAME_TIME_REASON::MissedCompositeLong);
|
||||
} else if (fullPaintTime >= TimeDuration::FromMilliseconds(10)) {
|
||||
Telemetry::AccumulateCategorical(
|
||||
LABELS_CONTENT_FRAME_TIME_REASON::MissedCompositeMid);
|
||||
} else if (fullPaintTime >= TimeDuration::FromMilliseconds(5)) {
|
||||
Telemetry::AccumulateCategorical(
|
||||
LABELS_CONTENT_FRAME_TIME_REASON::MissedCompositeLow);
|
||||
} else {
|
||||
Telemetry::AccumulateCategorical(
|
||||
LABELS_CONTENT_FRAME_TIME_REASON::MissedComposite);
|
||||
}
|
||||
} else {
|
||||
// Composite start on time, but must have taken too long.
|
||||
Telemetry::AccumulateCategorical(
|
||||
LABELS_CONTENT_FRAME_TIME_REASON::SlowComposite);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#if defined(ENABLE_FRAME_LATENCY_LOG)
|
||||
|
|
|
@ -1 +1 @@
|
|||
b298150b65db9e80ec15aff6877ca3277cb79f92
|
||||
1b226534099a24c741e9827c4612eee1ec12d4ee
|
||||
|
|
|
@ -4,8 +4,8 @@
|
|||
|
||||
use api::{DeviceRect, FilterOp, MixBlendMode, PipelineId, PremultipliedColorF, PictureRect, PicturePoint, WorldPoint};
|
||||
use api::{DeviceIntRect, DevicePoint, LayoutRect, PictureToRasterTransform, LayoutPixel, PropertyBinding, PropertyBindingId};
|
||||
use api::{DevicePixelScale, RasterRect, RasterSpace, ColorF, ImageKey, DirtyRect, WorldSize, LayoutSize, ClipMode};
|
||||
use api::{PicturePixel, RasterPixel, WorldPixel, WorldRect, ImageFormat, ImageDescriptor, WorldVector2D};
|
||||
use api::{DevicePixelScale, RasterRect, RasterSpace, ColorF, ImageKey, DirtyRect, WorldSize, ClipMode};
|
||||
use api::{PicturePixel, RasterPixel, WorldPixel, WorldRect, ImageFormat, ImageDescriptor, WorldVector2D, LayoutPoint};
|
||||
use box_shadow::{BLUR_SAMPLE_SCALE};
|
||||
use clip::{ClipNodeCollector, ClipStore, ClipChainId, ClipChainNode, ClipItem};
|
||||
use clip_scroll_tree::{ROOT_SPATIAL_NODE_INDEX, ClipScrollTree, SpatialNodeIndex, CoordinateSystemId};
|
||||
|
@ -13,13 +13,13 @@ use device::TextureFilter;
|
|||
use euclid::{TypedScale, vec3, TypedRect, TypedPoint2D, TypedSize2D};
|
||||
use euclid::approxeq::ApproxEq;
|
||||
use intern::ItemUid;
|
||||
use internal_types::{FastHashMap, PlaneSplitter};
|
||||
use internal_types::{FastHashMap, FastHashSet, PlaneSplitter};
|
||||
use frame_builder::{FrameBuildingContext, FrameBuildingState, PictureState, PictureContext};
|
||||
use gpu_cache::{GpuCache, GpuCacheAddress, GpuCacheHandle};
|
||||
use gpu_types::{TransformPalette, TransformPaletteId, UvRectKind};
|
||||
use plane_split::{Clipper, Polygon, Splitter};
|
||||
use prim_store::{PictureIndex, PrimitiveInstance, SpaceMapper, VisibleFace, PrimitiveInstanceKind};
|
||||
use prim_store::{get_raster_rects, CoordinateSpaceMapping, VectorKey};
|
||||
use prim_store::{get_raster_rects, CoordinateSpaceMapping};
|
||||
use prim_store::{OpacityBindingStorage, ImageInstanceStorage, OpacityBindingIndex};
|
||||
use print_tree::PrintTreePrinter;
|
||||
use render_backend::FrameResources;
|
||||
|
@ -128,6 +128,10 @@ pub struct Tile {
|
|||
/// The tile id is stable between display lists and / or frames,
|
||||
/// if the tile is retained. Useful for debugging tile evictions.
|
||||
id: TileId,
|
||||
/// The set of transforms that affect primitives on this tile we
|
||||
/// care about. Stored as a set here, and then collected, sorted
|
||||
/// and converted to transform key values during post_update.
|
||||
transforms: FastHashSet<SpatialNodeIndex>,
|
||||
}
|
||||
|
||||
impl Tile {
|
||||
|
@ -143,12 +147,14 @@ impl Tile {
|
|||
handle: TextureCacheHandle::invalid(),
|
||||
descriptor: TileDescriptor::new(),
|
||||
is_valid: false,
|
||||
transforms: FastHashSet::default(),
|
||||
id,
|
||||
}
|
||||
}
|
||||
|
||||
/// Clear the dependencies for a tile.
|
||||
fn clear(&mut self) {
|
||||
self.transforms.clear();
|
||||
self.descriptor.clear();
|
||||
}
|
||||
}
|
||||
|
@ -158,8 +164,8 @@ impl Tile {
|
|||
pub struct PrimitiveDescriptor {
|
||||
/// Uniquely identifies the content of the primitive template.
|
||||
prim_uid: ItemUid,
|
||||
/// The origin in world space of this primitive.
|
||||
origin: WorldPoint,
|
||||
/// The origin in local space of this primitive.
|
||||
origin: LayoutPoint,
|
||||
/// The first clip in the clip_uids array of clips that affect this tile.
|
||||
first_clip: u16,
|
||||
/// The number of clips that affect this primitive instance.
|
||||
|
@ -179,10 +185,10 @@ pub struct TileDescriptor {
|
|||
/// to uniquely describe the content of the clip node.
|
||||
clip_uids: ComparableVec<ItemUid>,
|
||||
|
||||
/// List of tile relative offsets of the clip node origins. This
|
||||
/// List of local offsets of the clip node origins. This
|
||||
/// ensures that if a clip node is supplied but has a different
|
||||
/// transform between frames that the tile is invalidated.
|
||||
clip_vertices: ComparableVec<VectorKey>,
|
||||
clip_vertices: ComparableVec<LayoutPoint>,
|
||||
|
||||
/// List of image keys that this tile depends on.
|
||||
image_keys: ComparableVec<ImageKey>,
|
||||
|
@ -196,6 +202,10 @@ pub struct TileDescriptor {
|
|||
|
||||
/// List of the currently valid rectangles for each primitive.
|
||||
current_rects: Vec<WorldRect>,
|
||||
|
||||
/// List of the (quantized) transforms that we care about
|
||||
/// tracking for this tile.
|
||||
transforms: ComparableVec<TransformKey>,
|
||||
}
|
||||
|
||||
impl TileDescriptor {
|
||||
|
@ -208,6 +218,7 @@ impl TileDescriptor {
|
|||
image_keys: ComparableVec::new(),
|
||||
needed_rects: Vec::new(),
|
||||
current_rects: Vec::new(),
|
||||
transforms: ComparableVec::new(),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -220,6 +231,7 @@ impl TileDescriptor {
|
|||
self.opacity_bindings.reset();
|
||||
self.image_keys.reset();
|
||||
self.needed_rects.clear();
|
||||
self.transforms.reset();
|
||||
}
|
||||
|
||||
/// Check if the dependencies of this tile are valid.
|
||||
|
@ -249,6 +261,7 @@ impl TileDescriptor {
|
|||
self.clip_uids.is_valid() &&
|
||||
self.clip_vertices.is_valid() &&
|
||||
self.prims.is_valid() &&
|
||||
self.transforms.is_valid() &&
|
||||
rects_valid
|
||||
}
|
||||
}
|
||||
|
@ -689,9 +702,10 @@ impl TileCache {
|
|||
// Build the list of resources that this primitive has dependencies on.
|
||||
let mut opacity_bindings: SmallVec<[PropertyBindingId; 4]> = SmallVec::new();
|
||||
let mut clip_chain_uids: SmallVec<[ItemUid; 8]> = SmallVec::new();
|
||||
let mut clip_vertices: SmallVec<[WorldPoint; 8]> = SmallVec::new();
|
||||
let mut clip_vertices: SmallVec<[LayoutPoint; 8]> = SmallVec::new();
|
||||
let mut image_keys: SmallVec<[ImageKey; 8]> = SmallVec::new();
|
||||
let mut current_clip_chain_id = prim_instance.clip_chain_id;
|
||||
let mut clip_spatial_nodes = FastHashSet::default();
|
||||
|
||||
// Some primitives can not be cached (e.g. external video images)
|
||||
let is_cacheable = prim_instance.is_cacheable(
|
||||
|
@ -819,19 +833,9 @@ impl TileCache {
|
|||
};
|
||||
|
||||
if add_to_clip_deps {
|
||||
// TODO(gw): Constructing a rect here rather than mapping a point
|
||||
// is wasteful. We can optimize this by extending the
|
||||
// SpaceMapper struct to support mapping a point.
|
||||
let local_rect = LayoutRect::new(
|
||||
clip_chain_node.local_pos,
|
||||
LayoutSize::zero(),
|
||||
);
|
||||
|
||||
if let Some(clip_world_rect) = self.map_local_to_world.map(&local_rect) {
|
||||
clip_vertices.push(clip_world_rect.origin);
|
||||
}
|
||||
|
||||
clip_vertices.push(clip_chain_node.local_pos);
|
||||
clip_chain_uids.push(clip_chain_node.handle.uid());
|
||||
clip_spatial_nodes.insert(clip_chain_node.spatial_node_index);
|
||||
}
|
||||
|
||||
current_clip_chain_id = clip_chain_node.parent_clip_chain_id;
|
||||
|
@ -891,37 +895,19 @@ impl TileCache {
|
|||
// // Include any opacity bindings this primitive depends on.
|
||||
tile.descriptor.opacity_bindings.extend_from_slice(&opacity_bindings);
|
||||
|
||||
// For the primitive origin, store the world origin relative to
|
||||
// the world origin of the containing picture. This ensures that
|
||||
// a tile with primitives in the same coordinate system as the
|
||||
// container picture itself, but different offsets relative to
|
||||
// the containing picture are correctly invalidated. It does this
|
||||
// while still maintaining the property of keeping the same hash
|
||||
// for different display lists where the local origin is different
|
||||
// but the primitives themselves are at the same relative position.
|
||||
let origin = WorldPoint::new(
|
||||
world_rect.origin.x - tile.world_rect.origin.x,
|
||||
world_rect.origin.y - tile.world_rect.origin.y
|
||||
);
|
||||
|
||||
// Update the tile descriptor, used for tile comparison during scene swaps.
|
||||
tile.descriptor.prims.push(PrimitiveDescriptor {
|
||||
prim_uid: prim_instance.uid(),
|
||||
origin,
|
||||
origin: prim_instance.prim_origin,
|
||||
first_clip: tile.descriptor.clip_uids.len() as u16,
|
||||
clip_count: clip_chain_uids.len() as u16,
|
||||
});
|
||||
tile.descriptor.clip_uids.extend_from_slice(&clip_chain_uids);
|
||||
tile.descriptor.clip_vertices.extend_from_slice(&clip_vertices);
|
||||
|
||||
// Store tile relative clip vertices.
|
||||
// TODO(gw): We might need to quantize these to avoid
|
||||
// invalidations due to FP accuracy.
|
||||
for clip_vertex in &clip_vertices {
|
||||
let clip_vertex = VectorKey {
|
||||
x: clip_vertex.x - tile.world_rect.origin.x,
|
||||
y: clip_vertex.y - tile.world_rect.origin.y,
|
||||
};
|
||||
tile.descriptor.clip_vertices.push(clip_vertex);
|
||||
tile.transforms.insert(prim_instance.spatial_node_index);
|
||||
for spatial_node_index in &clip_spatial_nodes {
|
||||
tile.transforms.insert(*spatial_node_index);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -967,6 +953,18 @@ impl TileCache {
|
|||
|
||||
// Step through each tile and invalidate if the dependencies have changed.
|
||||
for (i, tile) in self.tiles.iter_mut().enumerate() {
|
||||
// Update tile transforms
|
||||
let mut transform_spatial_nodes: Vec<SpatialNodeIndex> = tile.transforms.drain().collect();
|
||||
transform_spatial_nodes.sort();
|
||||
for spatial_node_index in transform_spatial_nodes {
|
||||
let mapping: CoordinateSpaceMapping<LayoutPixel, PicturePixel> = CoordinateSpaceMapping::new(
|
||||
self.spatial_node_index,
|
||||
spatial_node_index,
|
||||
frame_context.clip_scroll_tree,
|
||||
).expect("todo: handle invalid mappings");
|
||||
tile.descriptor.transforms.push(mapping.into());
|
||||
}
|
||||
|
||||
// Invalidate if the backing texture was evicted.
|
||||
if resource_cache.texture_cache.is_allocated(&tile.handle) {
|
||||
// Request the backing texture so it won't get evicted this frame.
|
||||
|
@ -1000,7 +998,7 @@ impl TileCache {
|
|||
}
|
||||
} else {
|
||||
// Add the tile rect to the dirty rect.
|
||||
dirty_world_rect = dirty_world_rect.union(&tile.world_rect);
|
||||
dirty_world_rect = dirty_world_rect.union(&visible_rect);
|
||||
|
||||
// Ensure that this texture is allocated.
|
||||
resource_cache.texture_cache.update(
|
||||
|
|
|
@ -148,6 +148,7 @@ static void Shutdown();
|
|||
#include "nsControllerCommandTable.h"
|
||||
|
||||
#include "mozilla/TextInputProcessor.h"
|
||||
#include "mozilla/ScriptableContentIterator.h"
|
||||
|
||||
using namespace mozilla;
|
||||
using namespace mozilla::dom;
|
||||
|
@ -220,6 +221,7 @@ NS_GENERIC_FACTORY_SINGLETON_CONSTRUCTOR(nsIPresentationService,
|
|||
NS_GENERIC_FACTORY_CONSTRUCTOR(PresentationTCPSessionTransport)
|
||||
NS_GENERIC_FACTORY_CONSTRUCTOR_INIT(NotificationTelemetryService, Init)
|
||||
NS_GENERIC_FACTORY_CONSTRUCTOR(PushNotifier)
|
||||
NS_GENERIC_FACTORY_CONSTRUCTOR(ScriptableContentIterator)
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
|
@ -519,6 +521,8 @@ NS_DEFINE_NAMED_CID(TEXT_INPUT_PROCESSOR_CID);
|
|||
|
||||
NS_DEFINE_NAMED_CID(NS_SCRIPTERROR_CID);
|
||||
|
||||
NS_DEFINE_NAMED_CID(SCRIPTABLE_CONTENT_ITERATOR_CID);
|
||||
|
||||
static nsresult LocalStorageManagerConstructor(nsISupports* aOuter,
|
||||
REFNSIID aIID, void** aResult) {
|
||||
if (NextGenLocalStorageEnabled()) {
|
||||
|
@ -604,6 +608,7 @@ static const mozilla::Module::CIDEntry kLayoutCIDs[] = {
|
|||
{ &kPRESENTATION_TCP_SESSION_TRANSPORT_CID, false, nullptr, PresentationTCPSessionTransportConstructor },
|
||||
{ &kTEXT_INPUT_PROCESSOR_CID, false, nullptr, TextInputProcessorConstructor },
|
||||
{ &kNS_SCRIPTERROR_CID, false, nullptr, nsScriptErrorConstructor },
|
||||
{ &kSCRIPTABLE_CONTENT_ITERATOR_CID, false, nullptr, ScriptableContentIteratorConstructor },
|
||||
{ nullptr }
|
||||
// clang-format on
|
||||
};
|
||||
|
@ -676,6 +681,7 @@ static const mozilla::Module::ContractIDEntry kLayoutContracts[] = {
|
|||
{ PRESENTATION_TCP_SESSION_TRANSPORT_CONTRACTID, &kPRESENTATION_TCP_SESSION_TRANSPORT_CID },
|
||||
{ "@mozilla.org/text-input-processor;1", &kTEXT_INPUT_PROCESSOR_CID },
|
||||
{ NS_SCRIPTERROR_CONTRACTID, &kNS_SCRIPTERROR_CID },
|
||||
{ "@mozilla.org/scriptable-content-iterator;1", &kSCRIPTABLE_CONTENT_ITERATOR_CID },
|
||||
{ nullptr }
|
||||
};
|
||||
|
||||
|
|
|
@ -105,9 +105,9 @@ SERIALIZED_PREDEFINED_TYPES = [
|
|||
"JustifyItems",
|
||||
"JustifySelf",
|
||||
"Length",
|
||||
"LengthOrPercentage",
|
||||
"LengthPercentage",
|
||||
"NonNegativeLength",
|
||||
"NonNegativeLengthOrPercentage",
|
||||
"NonNegativeLengthPercentage",
|
||||
"ListStyleType",
|
||||
"OffsetPath",
|
||||
"Opacity",
|
||||
|
|
|
@ -54,7 +54,7 @@ div.sidebarContents {
|
|||
top: 0.5em;
|
||||
}
|
||||
|
||||
div.index {
|
||||
div.sidebarItem {
|
||||
padding: 0.5em;
|
||||
margin: 1em 0em;
|
||||
border: 1px solid ThreeDShadow;
|
||||
|
@ -64,13 +64,17 @@ div.index {
|
|||
-moz-user-select: none; /* no need to include this when cutting+pasting */
|
||||
}
|
||||
|
||||
ul.indexList {
|
||||
input.filterInput {
|
||||
width: calc(100% - 1em);
|
||||
}
|
||||
|
||||
ul.index {
|
||||
list-style-position: inside;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
ul.indexList > li {
|
||||
ul.index > li {
|
||||
padding-left: 0.5em;
|
||||
}
|
||||
|
||||
|
@ -85,7 +89,7 @@ div.opsRow {
|
|||
display: inline-block;
|
||||
}
|
||||
|
||||
div.opsRowLabel, div.indexLabel {
|
||||
div.opsRowLabel, div.sidebarLabel {
|
||||
display: block;
|
||||
margin-bottom: 0.2em;
|
||||
font-weight: bold;
|
||||
|
|
|
@ -211,6 +211,15 @@ VARCACHE_PREF(
|
|||
bool, true
|
||||
)
|
||||
|
||||
// Whether we conform to Input Events Level 1 or Input Events Level 2.
|
||||
// true: conforming to Level 1
|
||||
// false: conforming to Level 2
|
||||
VARCACHE_PREF(
|
||||
"dom.input_events.conform_to_level_1",
|
||||
dom_input_events_conform_to_level_1,
|
||||
bool, true
|
||||
)
|
||||
|
||||
// NOTE: This preference is used in unit tests. If it is removed or its default
|
||||
// value changes, please update test_sharedMap_var_caches.js accordingly.
|
||||
VARCACHE_PREF(
|
||||
|
|
|
@ -233,6 +233,9 @@ pref("dom.keyboardevent.keypress.hack.dispatch_non_printable_keys", "");
|
|||
// check its explanation for the detail.
|
||||
pref("dom.keyboardevent.keypress.hack.use_legacy_keycode_and_charcode", "");
|
||||
|
||||
// Whether InputEvent.inputType is enabled.
|
||||
pref("dom.inputevent.inputtype.enabled", true);
|
||||
|
||||
// Whether the WebMIDI API is enabled
|
||||
pref("dom.webmidi.enabled", false);
|
||||
|
||||
|
|
|
@ -543,7 +543,7 @@ pub fn parse_legacy_color(mut input: &str) -> Result<RGBA, ()> {
|
|||
/// Parses a [dimension value][dim]. If unparseable, `Auto` is returned.
|
||||
///
|
||||
/// [dim]: https://html.spec.whatwg.org/multipage/#rules-for-parsing-dimension-values
|
||||
// TODO: this function can be rewritten to return Result<LengthOrPercentage, _>
|
||||
// TODO: this function can be rewritten to return Result<LengthPercentage, _>
|
||||
pub fn parse_length(mut value: &str) -> LengthOrPercentageOrAuto {
|
||||
// Steps 1 & 2 are not relevant
|
||||
|
||||
|
|
|
@ -20,9 +20,9 @@ use crate::stylesheets::{Origin, RulesMutateError};
|
|||
use crate::values::computed::image::LineDirection;
|
||||
use crate::values::computed::transform::Matrix3D;
|
||||
use crate::values::computed::url::ComputedImageUrl;
|
||||
use crate::values::computed::{Angle, CalcLengthOrPercentage, Gradient, Image};
|
||||
use crate::values::computed::{Integer, LengthOrPercentage};
|
||||
use crate::values::computed::{LengthOrPercentageOrAuto, NonNegativeLengthOrPercentageOrAuto};
|
||||
use crate::values::computed::{Angle, Gradient, Image};
|
||||
use crate::values::computed::{Integer, LengthPercentage};
|
||||
use crate::values::computed::{LengthPercentageOrAuto, NonNegativeLengthPercentageOrAuto};
|
||||
use crate::values::computed::{Percentage, TextAlign};
|
||||
use crate::values::generics::box_::VerticalAlign;
|
||||
use crate::values::generics::grid::{TrackListValue, TrackSize};
|
||||
|
@ -31,9 +31,10 @@ use crate::values::generics::rect::Rect;
|
|||
use crate::values::generics::NonNegative;
|
||||
use app_units::Au;
|
||||
use std::f32::consts::PI;
|
||||
use style_traits::values::specified::AllowedNumericType;
|
||||
|
||||
impl From<CalcLengthOrPercentage> for nsStyleCoord_CalcValue {
|
||||
fn from(other: CalcLengthOrPercentage) -> nsStyleCoord_CalcValue {
|
||||
impl From<LengthPercentage> for nsStyleCoord_CalcValue {
|
||||
fn from(other: LengthPercentage) -> nsStyleCoord_CalcValue {
|
||||
let has_percentage = other.percentage.is_some();
|
||||
nsStyleCoord_CalcValue {
|
||||
mLength: other.unclamped_length().to_i32_au(),
|
||||
|
@ -43,82 +44,44 @@ impl From<CalcLengthOrPercentage> for nsStyleCoord_CalcValue {
|
|||
}
|
||||
}
|
||||
|
||||
impl From<nsStyleCoord_CalcValue> for CalcLengthOrPercentage {
|
||||
fn from(other: nsStyleCoord_CalcValue) -> CalcLengthOrPercentage {
|
||||
impl From<nsStyleCoord_CalcValue> for LengthPercentage {
|
||||
fn from(other: nsStyleCoord_CalcValue) -> LengthPercentage {
|
||||
let percentage = if other.mHasPercent {
|
||||
Some(Percentage(other.mPercent))
|
||||
} else {
|
||||
None
|
||||
};
|
||||
Self::new(Au(other.mLength).into(), percentage)
|
||||
Self::with_clamping_mode(
|
||||
Au(other.mLength).into(),
|
||||
percentage,
|
||||
AllowedNumericType::All,
|
||||
/* was_calc = */ true,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<LengthOrPercentage> for nsStyleCoord_CalcValue {
|
||||
fn from(other: LengthOrPercentage) -> nsStyleCoord_CalcValue {
|
||||
match other {
|
||||
LengthOrPercentage::Length(px) => nsStyleCoord_CalcValue {
|
||||
mLength: px.to_i32_au(),
|
||||
mPercent: 0.0,
|
||||
mHasPercent: false,
|
||||
},
|
||||
LengthOrPercentage::Percentage(pc) => nsStyleCoord_CalcValue {
|
||||
mLength: 0,
|
||||
mPercent: pc.0,
|
||||
mHasPercent: true,
|
||||
},
|
||||
LengthOrPercentage::Calc(calc) => calc.into(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl LengthOrPercentageOrAuto {
|
||||
impl LengthPercentageOrAuto {
|
||||
/// Convert this value in an appropriate `nsStyleCoord::CalcValue`.
|
||||
pub fn to_calc_value(&self) -> Option<nsStyleCoord_CalcValue> {
|
||||
match *self {
|
||||
LengthOrPercentageOrAuto::Length(px) => Some(nsStyleCoord_CalcValue {
|
||||
mLength: px.to_i32_au(),
|
||||
mPercent: 0.0,
|
||||
mHasPercent: false,
|
||||
}),
|
||||
LengthOrPercentageOrAuto::Percentage(pc) => Some(nsStyleCoord_CalcValue {
|
||||
mLength: 0,
|
||||
mPercent: pc.0,
|
||||
mHasPercent: true,
|
||||
}),
|
||||
LengthOrPercentageOrAuto::Calc(calc) => Some(calc.into()),
|
||||
LengthOrPercentageOrAuto::Auto => None,
|
||||
LengthPercentageOrAuto::LengthPercentage(len) => Some(From::from(len)),
|
||||
LengthPercentageOrAuto::Auto => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<nsStyleCoord_CalcValue> for LengthOrPercentage {
|
||||
fn from(other: nsStyleCoord_CalcValue) -> LengthOrPercentage {
|
||||
match (other.mHasPercent, other.mLength) {
|
||||
(false, _) => LengthOrPercentage::Length(Au(other.mLength).into()),
|
||||
(true, 0) => LengthOrPercentage::Percentage(Percentage(other.mPercent)),
|
||||
_ => LengthOrPercentage::Calc(other.into()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<nsStyleCoord_CalcValue> for LengthOrPercentageOrAuto {
|
||||
fn from(other: nsStyleCoord_CalcValue) -> LengthOrPercentageOrAuto {
|
||||
match (other.mHasPercent, other.mLength) {
|
||||
(false, _) => LengthOrPercentageOrAuto::Length(Au(other.mLength).into()),
|
||||
(true, 0) => LengthOrPercentageOrAuto::Percentage(Percentage(other.mPercent)),
|
||||
_ => LengthOrPercentageOrAuto::Calc(other.into()),
|
||||
}
|
||||
impl From<nsStyleCoord_CalcValue> for LengthPercentageOrAuto {
|
||||
fn from(other: nsStyleCoord_CalcValue) -> LengthPercentageOrAuto {
|
||||
LengthPercentageOrAuto::LengthPercentage(LengthPercentage::from(other))
|
||||
}
|
||||
}
|
||||
|
||||
// FIXME(emilio): A lot of these impl From should probably become explicit or
|
||||
// disappear as we move more stuff to cbindgen.
|
||||
impl From<nsStyleCoord_CalcValue> for NonNegativeLengthOrPercentageOrAuto {
|
||||
impl From<nsStyleCoord_CalcValue> for NonNegativeLengthPercentageOrAuto {
|
||||
fn from(other: nsStyleCoord_CalcValue) -> Self {
|
||||
use style_traits::values::specified::AllowedNumericType;
|
||||
NonNegative(if other.mLength < 0 || other.mPercent < 0. {
|
||||
LengthOrPercentageOrAuto::Calc(CalcLengthOrPercentage::with_clamping_mode(
|
||||
NonNegative(
|
||||
LengthPercentageOrAuto::LengthPercentage(LengthPercentage::with_clamping_mode(
|
||||
Au(other.mLength).into(),
|
||||
if other.mHasPercent {
|
||||
Some(Percentage(other.mPercent))
|
||||
|
@ -126,10 +89,9 @@ impl From<nsStyleCoord_CalcValue> for NonNegativeLengthOrPercentageOrAuto {
|
|||
None
|
||||
},
|
||||
AllowedNumericType::NonNegative,
|
||||
/* was_calc = */ true,
|
||||
))
|
||||
} else {
|
||||
other.into()
|
||||
})
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -139,24 +101,17 @@ impl From<Angle> for CoordDataValue {
|
|||
}
|
||||
}
|
||||
|
||||
fn line_direction(horizontal: LengthOrPercentage, vertical: LengthOrPercentage) -> LineDirection {
|
||||
fn line_direction(horizontal: LengthPercentage, vertical: LengthPercentage) -> LineDirection {
|
||||
use crate::values::computed::position::Position;
|
||||
use crate::values::specified::position::{X, Y};
|
||||
|
||||
let horizontal_percentage = match horizontal {
|
||||
LengthOrPercentage::Percentage(percentage) => Some(percentage.0),
|
||||
_ => None,
|
||||
};
|
||||
|
||||
let vertical_percentage = match vertical {
|
||||
LengthOrPercentage::Percentage(percentage) => Some(percentage.0),
|
||||
_ => None,
|
||||
};
|
||||
let horizontal_percentage = horizontal.as_percentage();
|
||||
let vertical_percentage = vertical.as_percentage();
|
||||
|
||||
let horizontal_as_corner = horizontal_percentage.and_then(|percentage| {
|
||||
if percentage == 0.0 {
|
||||
if percentage.0 == 0.0 {
|
||||
Some(X::Left)
|
||||
} else if percentage == 1.0 {
|
||||
} else if percentage.0 == 1.0 {
|
||||
Some(X::Right)
|
||||
} else {
|
||||
None
|
||||
|
@ -164,9 +119,9 @@ fn line_direction(horizontal: LengthOrPercentage, vertical: LengthOrPercentage)
|
|||
});
|
||||
|
||||
let vertical_as_corner = vertical_percentage.and_then(|percentage| {
|
||||
if percentage == 0.0 {
|
||||
if percentage.0 == 0.0 {
|
||||
Some(Y::Top)
|
||||
} else if percentage == 1.0 {
|
||||
} else if percentage.0 == 1.0 {
|
||||
Some(Y::Bottom)
|
||||
} else {
|
||||
None
|
||||
|
@ -178,13 +133,13 @@ fn line_direction(horizontal: LengthOrPercentage, vertical: LengthOrPercentage)
|
|||
}
|
||||
|
||||
if let Some(hc) = horizontal_as_corner {
|
||||
if vertical_percentage == Some(0.5) {
|
||||
if vertical_percentage == Some(Percentage(0.5)) {
|
||||
return LineDirection::Horizontal(hc);
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(vc) = vertical_as_corner {
|
||||
if horizontal_percentage == Some(0.5) {
|
||||
if horizontal_percentage == Some(Percentage(0.5)) {
|
||||
return LineDirection::Vertical(vc);
|
||||
}
|
||||
}
|
||||
|
@ -512,8 +467,8 @@ impl nsStyleImage {
|
|||
.as_ref()
|
||||
.unwrap();
|
||||
let angle = Angle::from_gecko_style_coord(&gecko_gradient.mAngle);
|
||||
let horizontal_style = LengthOrPercentage::from_gecko_style_coord(&gecko_gradient.mBgPosX);
|
||||
let vertical_style = LengthOrPercentage::from_gecko_style_coord(&gecko_gradient.mBgPosY);
|
||||
let horizontal_style = LengthPercentage::from_gecko_style_coord(&gecko_gradient.mBgPosX);
|
||||
let vertical_style = LengthPercentage::from_gecko_style_coord(&gecko_gradient.mBgPosY);
|
||||
|
||||
let kind = match gecko_gradient.mShape as u32 {
|
||||
structs::NS_STYLE_GRADIENT_SHAPE_LINEAR => {
|
||||
|
@ -574,20 +529,20 @@ impl nsStyleImage {
|
|||
structs::NS_STYLE_GRADIENT_SHAPE_ELLIPTICAL => {
|
||||
let length_percentage_keyword = match gecko_gradient.mSize as u32 {
|
||||
structs::NS_STYLE_GRADIENT_SIZE_EXPLICIT_SIZE => match (
|
||||
LengthOrPercentage::from_gecko_style_coord(
|
||||
LengthPercentage::from_gecko_style_coord(
|
||||
&gecko_gradient.mRadiusX,
|
||||
),
|
||||
LengthOrPercentage::from_gecko_style_coord(
|
||||
LengthPercentage::from_gecko_style_coord(
|
||||
&gecko_gradient.mRadiusY,
|
||||
),
|
||||
) {
|
||||
(Some(x), Some(y)) => Ellipse::Radii(x, y),
|
||||
_ => {
|
||||
debug_assert!(false,
|
||||
"mRadiusX, mRadiusY could not convert to LengthOrPercentage");
|
||||
"mRadiusX, mRadiusY could not convert to LengthPercentage");
|
||||
Ellipse::Radii(
|
||||
LengthOrPercentage::zero(),
|
||||
LengthOrPercentage::zero(),
|
||||
LengthPercentage::zero(),
|
||||
LengthPercentage::zero(),
|
||||
)
|
||||
},
|
||||
},
|
||||
|
@ -606,11 +561,11 @@ impl nsStyleImage {
|
|||
_ => {
|
||||
debug_assert!(
|
||||
false,
|
||||
"mRadiusX, mRadiusY could not convert to LengthOrPercentage"
|
||||
"mRadiusX, mRadiusY could not convert to LengthPercentage"
|
||||
);
|
||||
Position {
|
||||
horizontal: LengthOrPercentage::zero(),
|
||||
vertical: LengthOrPercentage::zero(),
|
||||
horizontal: LengthPercentage::zero(),
|
||||
vertical: LengthPercentage::zero(),
|
||||
}
|
||||
},
|
||||
};
|
||||
|
@ -625,13 +580,13 @@ impl nsStyleImage {
|
|||
.map(|ref stop| {
|
||||
if stop.mIsInterpolationHint {
|
||||
GradientItem::InterpolationHint(
|
||||
LengthOrPercentage::from_gecko_style_coord(&stop.mLocation)
|
||||
.expect("mLocation could not convert to LengthOrPercentage"),
|
||||
LengthPercentage::from_gecko_style_coord(&stop.mLocation)
|
||||
.expect("mLocation could not convert to LengthPercentage"),
|
||||
)
|
||||
} else {
|
||||
GradientItem::ColorStop(ColorStop {
|
||||
color: stop.mColor.into(),
|
||||
position: LengthOrPercentage::from_gecko_style_coord(&stop.mLocation),
|
||||
position: LengthPercentage::from_gecko_style_coord(&stop.mLocation),
|
||||
})
|
||||
}
|
||||
})
|
||||
|
@ -670,7 +625,7 @@ pub mod basic_shape {
|
|||
BasicShape, ClippingShape, FloatAreaShape, ShapeRadius,
|
||||
};
|
||||
use crate::values::computed::border::{BorderCornerRadius, BorderRadius};
|
||||
use crate::values::computed::length::LengthOrPercentage;
|
||||
use crate::values::computed::length::LengthPercentage;
|
||||
use crate::values::computed::motion::OffsetPath;
|
||||
use crate::values::computed::position;
|
||||
use crate::values::computed::url::ComputedUrl;
|
||||
|
@ -787,10 +742,10 @@ pub mod basic_shape {
|
|||
fn from(other: &'a StyleBasicShape) -> Self {
|
||||
match other.mType {
|
||||
StyleBasicShapeType::Inset => {
|
||||
let t = LengthOrPercentage::from_gecko_style_coord(&other.mCoordinates[0]);
|
||||
let r = LengthOrPercentage::from_gecko_style_coord(&other.mCoordinates[1]);
|
||||
let b = LengthOrPercentage::from_gecko_style_coord(&other.mCoordinates[2]);
|
||||
let l = LengthOrPercentage::from_gecko_style_coord(&other.mCoordinates[3]);
|
||||
let t = LengthPercentage::from_gecko_style_coord(&other.mCoordinates[0]);
|
||||
let r = LengthPercentage::from_gecko_style_coord(&other.mCoordinates[1]);
|
||||
let b = LengthPercentage::from_gecko_style_coord(&other.mCoordinates[2]);
|
||||
let l = LengthPercentage::from_gecko_style_coord(&other.mCoordinates[3]);
|
||||
let round: BorderRadius = (&other.mRadius).into();
|
||||
let round = if round.all_zero() { None } else { Some(round) };
|
||||
let rect = Rect::new(
|
||||
|
@ -816,12 +771,12 @@ pub mod basic_shape {
|
|||
let x = 2 * i;
|
||||
let y = x + 1;
|
||||
coords.push(PolygonCoord(
|
||||
LengthOrPercentage::from_gecko_style_coord(&other.mCoordinates[x])
|
||||
LengthPercentage::from_gecko_style_coord(&other.mCoordinates[x])
|
||||
.expect(
|
||||
"polygon() coordinate should be a length, percentage, \
|
||||
or calc value",
|
||||
),
|
||||
LengthOrPercentage::from_gecko_style_coord(&other.mCoordinates[y])
|
||||
LengthPercentage::from_gecko_style_coord(&other.mCoordinates[y])
|
||||
.expect(
|
||||
"polygon() coordinate should be a length, percentage, \
|
||||
or calc value",
|
||||
|
@ -842,12 +797,12 @@ pub mod basic_shape {
|
|||
let get_corner = |index| {
|
||||
BorderCornerRadius::new(
|
||||
NonNegative(
|
||||
LengthOrPercentage::from_gecko_style_coord(&other.data_at(index)).expect(
|
||||
LengthPercentage::from_gecko_style_coord(&other.data_at(index)).expect(
|
||||
"<border-radius> should be a length, percentage, or calc value",
|
||||
),
|
||||
),
|
||||
NonNegative(
|
||||
LengthOrPercentage::from_gecko_style_coord(&other.data_at(index + 1))
|
||||
LengthPercentage::from_gecko_style_coord(&other.data_at(index + 1))
|
||||
.expect(
|
||||
"<border-radius> should be a length, percentage, or calc value",
|
||||
),
|
||||
|
@ -1003,11 +958,11 @@ impl From<Origin> for SheetType {
|
|||
}
|
||||
}
|
||||
|
||||
impl TrackSize<LengthOrPercentage> {
|
||||
impl TrackSize<LengthPercentage> {
|
||||
/// Return TrackSize from given two nsStyleCoord
|
||||
pub fn from_gecko_style_coords<T: CoordData>(gecko_min: &T, gecko_max: &T) -> Self {
|
||||
use crate::gecko_bindings::structs::root::nsStyleUnit;
|
||||
use crate::values::computed::length::LengthOrPercentage;
|
||||
use crate::values::computed::length::LengthPercentage;
|
||||
use crate::values::generics::grid::{TrackBreadth, TrackSize};
|
||||
|
||||
if gecko_min.unit() == nsStyleUnit::eStyleUnit_None {
|
||||
|
@ -1017,8 +972,8 @@ impl TrackSize<LengthOrPercentage> {
|
|||
gecko_max.unit() == nsStyleUnit::eStyleUnit_Calc
|
||||
);
|
||||
return TrackSize::FitContent(
|
||||
LengthOrPercentage::from_gecko_style_coord(gecko_max)
|
||||
.expect("gecko_max could not convert to LengthOrPercentage"),
|
||||
LengthPercentage::from_gecko_style_coord(gecko_max)
|
||||
.expect("gecko_max could not convert to LengthPercentage"),
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -1058,7 +1013,7 @@ impl TrackSize<LengthOrPercentage> {
|
|||
}
|
||||
}
|
||||
|
||||
impl TrackListValue<LengthOrPercentage, Integer> {
|
||||
impl TrackListValue<LengthPercentage, Integer> {
|
||||
/// Return TrackSize from given two nsStyleCoord
|
||||
pub fn from_gecko_style_coords<T: CoordData>(gecko_min: &T, gecko_max: &T) -> Self {
|
||||
TrackListValue::TrackSize(TrackSize::from_gecko_style_coords(gecko_min, gecko_max))
|
||||
|
|
|
@ -13,9 +13,9 @@ use crate::gecko_bindings::sugar::ns_style_coord::{CoordData, CoordDataMut, Coor
|
|||
use crate::media_queries::Device;
|
||||
use crate::values::computed::basic_shape::ShapeRadius as ComputedShapeRadius;
|
||||
use crate::values::computed::FlexBasis as ComputedFlexBasis;
|
||||
use crate::values::computed::{Angle, ExtremumLength, Length, LengthOrPercentage};
|
||||
use crate::values::computed::{LengthOrPercentageOrAuto, Percentage};
|
||||
use crate::values::computed::{LengthOrPercentageOrNone, Number, NumberOrPercentage};
|
||||
use crate::values::computed::{Angle, ExtremumLength, Length, LengthPercentage};
|
||||
use crate::values::computed::{LengthPercentageOrAuto, Percentage};
|
||||
use crate::values::computed::{LengthPercentageOrNone, Number, NumberOrPercentage};
|
||||
use crate::values::computed::{MaxLength as ComputedMaxLength, MozLength as ComputedMozLength};
|
||||
use crate::values::generics::basic_shape::ShapeRadius;
|
||||
use crate::values::generics::box_::Perspective;
|
||||
|
@ -146,21 +146,23 @@ impl GeckoStyleCoordConvertible for NumberOrPercentage {
|
|||
}
|
||||
}
|
||||
|
||||
impl GeckoStyleCoordConvertible for LengthOrPercentage {
|
||||
impl GeckoStyleCoordConvertible for LengthPercentage {
|
||||
fn to_gecko_style_coord<T: CoordDataMut>(&self, coord: &mut T) {
|
||||
let value = match *self {
|
||||
LengthOrPercentage::Length(px) => CoordDataValue::Coord(px.to_i32_au()),
|
||||
LengthOrPercentage::Percentage(p) => CoordDataValue::Percent(p.0),
|
||||
LengthOrPercentage::Calc(calc) => CoordDataValue::Calc(calc.into()),
|
||||
};
|
||||
coord.set_value(value);
|
||||
if self.was_calc {
|
||||
return coord.set_value(CoordDataValue::Calc((*self).into()))
|
||||
}
|
||||
debug_assert!(self.percentage.is_none() || self.unclamped_length() == Length::zero());
|
||||
if let Some(p) = self.percentage {
|
||||
return coord.set_value(CoordDataValue::Percent(p.0));
|
||||
}
|
||||
coord.set_value(CoordDataValue::Coord(self.unclamped_length().to_i32_au()))
|
||||
}
|
||||
|
||||
fn from_gecko_style_coord<T: CoordData>(coord: &T) -> Option<Self> {
|
||||
match coord.as_value() {
|
||||
CoordDataValue::Coord(coord) => Some(LengthOrPercentage::Length(Au(coord).into())),
|
||||
CoordDataValue::Percent(p) => Some(LengthOrPercentage::Percentage(Percentage(p))),
|
||||
CoordDataValue::Calc(calc) => Some(LengthOrPercentage::Calc(calc.into())),
|
||||
CoordDataValue::Coord(coord) => Some(LengthPercentage::new(Au(coord).into(), None)),
|
||||
CoordDataValue::Percent(p) => Some(LengthPercentage::new(Au(0).into(), Some(Percentage(p)))),
|
||||
CoordDataValue::Calc(calc) => Some(calc.into()),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
@ -179,50 +181,34 @@ impl GeckoStyleCoordConvertible for Length {
|
|||
}
|
||||
}
|
||||
|
||||
impl GeckoStyleCoordConvertible for LengthOrPercentageOrAuto {
|
||||
impl GeckoStyleCoordConvertible for LengthPercentageOrAuto {
|
||||
fn to_gecko_style_coord<T: CoordDataMut>(&self, coord: &mut T) {
|
||||
let value = match *self {
|
||||
LengthOrPercentageOrAuto::Length(px) => CoordDataValue::Coord(px.to_i32_au()),
|
||||
LengthOrPercentageOrAuto::Percentage(p) => CoordDataValue::Percent(p.0),
|
||||
LengthOrPercentageOrAuto::Auto => CoordDataValue::Auto,
|
||||
LengthOrPercentageOrAuto::Calc(calc) => CoordDataValue::Calc(calc.into()),
|
||||
};
|
||||
coord.set_value(value);
|
||||
match *self {
|
||||
LengthPercentageOrAuto::Auto => coord.set_value(CoordDataValue::Auto),
|
||||
LengthPercentageOrAuto::LengthPercentage(ref lp) => lp.to_gecko_style_coord(coord),
|
||||
}
|
||||
}
|
||||
|
||||
fn from_gecko_style_coord<T: CoordData>(coord: &T) -> Option<Self> {
|
||||
match coord.as_value() {
|
||||
CoordDataValue::Coord(coord) => {
|
||||
Some(LengthOrPercentageOrAuto::Length(Au(coord).into()))
|
||||
},
|
||||
CoordDataValue::Percent(p) => Some(LengthOrPercentageOrAuto::Percentage(Percentage(p))),
|
||||
CoordDataValue::Auto => Some(LengthOrPercentageOrAuto::Auto),
|
||||
CoordDataValue::Calc(calc) => Some(LengthOrPercentageOrAuto::Calc(calc.into())),
|
||||
_ => None,
|
||||
CoordDataValue::Auto => Some(LengthPercentageOrAuto::Auto),
|
||||
_ => LengthPercentage::from_gecko_style_coord(coord).map(LengthPercentageOrAuto::LengthPercentage),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl GeckoStyleCoordConvertible for LengthOrPercentageOrNone {
|
||||
impl GeckoStyleCoordConvertible for LengthPercentageOrNone {
|
||||
fn to_gecko_style_coord<T: CoordDataMut>(&self, coord: &mut T) {
|
||||
let value = match *self {
|
||||
LengthOrPercentageOrNone::Length(px) => CoordDataValue::Coord(px.to_i32_au()),
|
||||
LengthOrPercentageOrNone::Percentage(p) => CoordDataValue::Percent(p.0),
|
||||
LengthOrPercentageOrNone::None => CoordDataValue::None,
|
||||
LengthOrPercentageOrNone::Calc(calc) => CoordDataValue::Calc(calc.into()),
|
||||
};
|
||||
coord.set_value(value);
|
||||
match *self {
|
||||
LengthPercentageOrNone::None => coord.set_value(CoordDataValue::None),
|
||||
LengthPercentageOrNone::LengthPercentage(ref lp) => lp.to_gecko_style_coord(coord),
|
||||
}
|
||||
}
|
||||
|
||||
fn from_gecko_style_coord<T: CoordData>(coord: &T) -> Option<Self> {
|
||||
match coord.as_value() {
|
||||
CoordDataValue::Coord(coord) => {
|
||||
Some(LengthOrPercentageOrNone::Length(Au(coord).into()))
|
||||
},
|
||||
CoordDataValue::Percent(p) => Some(LengthOrPercentageOrNone::Percentage(Percentage(p))),
|
||||
CoordDataValue::None => Some(LengthOrPercentageOrNone::None),
|
||||
CoordDataValue::Calc(calc) => Some(LengthOrPercentageOrNone::Calc(calc.into())),
|
||||
_ => None,
|
||||
CoordDataValue::None => Some(LengthPercentageOrNone::None),
|
||||
_ => LengthPercentage::from_gecko_style_coord(coord).map(LengthPercentageOrNone::LengthPercentage),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -230,7 +216,7 @@ impl GeckoStyleCoordConvertible for LengthOrPercentageOrNone {
|
|||
impl<L: GeckoStyleCoordConvertible> GeckoStyleCoordConvertible for TrackBreadth<L> {
|
||||
fn to_gecko_style_coord<T: CoordDataMut>(&self, coord: &mut T) {
|
||||
match *self {
|
||||
TrackBreadth::Breadth(ref lop) => lop.to_gecko_style_coord(coord),
|
||||
TrackBreadth::Breadth(ref lp) => lp.to_gecko_style_coord(coord),
|
||||
TrackBreadth::Fr(fr) => coord.set_value(CoordDataValue::FlexFraction(fr)),
|
||||
TrackBreadth::Keyword(TrackKeyword::Auto) => coord.set_value(CoordDataValue::Auto),
|
||||
TrackBreadth::Keyword(TrackKeyword::MinContent) => coord.set_value(
|
||||
|
@ -271,7 +257,7 @@ impl GeckoStyleCoordConvertible for ComputedShapeRadius {
|
|||
ShapeRadius::FarthestSide => coord.set_value(CoordDataValue::Enumerated(
|
||||
StyleShapeRadius::FarthestSide as u32,
|
||||
)),
|
||||
ShapeRadius::Length(lop) => lop.to_gecko_style_coord(coord),
|
||||
ShapeRadius::Length(lp) => lp.to_gecko_style_coord(coord),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -377,14 +363,14 @@ impl GeckoStyleCoordConvertible for ExtremumLength {
|
|||
impl GeckoStyleCoordConvertible for ComputedMozLength {
|
||||
fn to_gecko_style_coord<T: CoordDataMut>(&self, coord: &mut T) {
|
||||
match *self {
|
||||
MozLength::LengthOrPercentageOrAuto(ref lopoa) => lopoa.to_gecko_style_coord(coord),
|
||||
MozLength::LengthPercentageOrAuto(ref lpoa) => lpoa.to_gecko_style_coord(coord),
|
||||
MozLength::ExtremumLength(ref e) => e.to_gecko_style_coord(coord),
|
||||
}
|
||||
}
|
||||
|
||||
fn from_gecko_style_coord<T: CoordData>(coord: &T) -> Option<Self> {
|
||||
LengthOrPercentageOrAuto::from_gecko_style_coord(coord)
|
||||
.map(MozLength::LengthOrPercentageOrAuto)
|
||||
LengthPercentageOrAuto::from_gecko_style_coord(coord)
|
||||
.map(MozLength::LengthPercentageOrAuto)
|
||||
.or_else(|| {
|
||||
ExtremumLength::from_gecko_style_coord(coord).map(MozLength::ExtremumLength)
|
||||
})
|
||||
|
@ -394,21 +380,21 @@ impl GeckoStyleCoordConvertible for ComputedMozLength {
|
|||
impl GeckoStyleCoordConvertible for ComputedMaxLength {
|
||||
fn to_gecko_style_coord<T: CoordDataMut>(&self, coord: &mut T) {
|
||||
match *self {
|
||||
MaxLength::LengthOrPercentageOrNone(ref lopon) => lopon.to_gecko_style_coord(coord),
|
||||
MaxLength::LengthPercentageOrNone(ref lpon) => lpon.to_gecko_style_coord(coord),
|
||||
MaxLength::ExtremumLength(ref e) => e.to_gecko_style_coord(coord),
|
||||
}
|
||||
}
|
||||
|
||||
fn from_gecko_style_coord<T: CoordData>(coord: &T) -> Option<Self> {
|
||||
LengthOrPercentageOrNone::from_gecko_style_coord(coord)
|
||||
.map(MaxLength::LengthOrPercentageOrNone)
|
||||
LengthPercentageOrNone::from_gecko_style_coord(coord)
|
||||
.map(MaxLength::LengthPercentageOrNone)
|
||||
.or_else(|| {
|
||||
ExtremumLength::from_gecko_style_coord(coord).map(MaxLength::ExtremumLength)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl GeckoStyleCoordConvertible for ScrollSnapPoint<LengthOrPercentage> {
|
||||
impl GeckoStyleCoordConvertible for ScrollSnapPoint<LengthPercentage> {
|
||||
fn to_gecko_style_coord<T: CoordDataMut>(&self, coord: &mut T) {
|
||||
match self.repeated() {
|
||||
None => coord.set_value(CoordDataValue::None),
|
||||
|
@ -423,8 +409,8 @@ impl GeckoStyleCoordConvertible for ScrollSnapPoint<LengthOrPercentage> {
|
|||
Some(match coord.unit() {
|
||||
nsStyleUnit::eStyleUnit_None => ScrollSnapPoint::None,
|
||||
_ => ScrollSnapPoint::Repeat(
|
||||
LengthOrPercentage::from_gecko_style_coord(coord)
|
||||
.expect("coord could not convert to LengthOrPercentage"),
|
||||
LengthPercentage::from_gecko_style_coord(coord)
|
||||
.expect("coord could not convert to LengthPercentage"),
|
||||
),
|
||||
})
|
||||
}
|
||||
|
|
|
@ -9,7 +9,7 @@ use crate::gecko_bindings::structs;
|
|||
use crate::gecko_bindings::structs::{nsCSSUnit, nsCSSValue};
|
||||
use crate::gecko_bindings::structs::{nsCSSValueList, nsCSSValue_Array};
|
||||
use crate::gecko_string_cache::Atom;
|
||||
use crate::values::computed::{Angle, Length, LengthOrPercentage, Percentage};
|
||||
use crate::values::computed::{Angle, Length, LengthPercentage, Percentage};
|
||||
use std::marker::PhantomData;
|
||||
use std::mem;
|
||||
use std::ops::{Index, IndexMut};
|
||||
|
@ -67,13 +67,16 @@ impl nsCSSValue {
|
|||
&*array
|
||||
}
|
||||
|
||||
/// Sets LengthOrPercentage value to this nsCSSValue.
|
||||
pub unsafe fn set_lop(&mut self, lop: LengthOrPercentage) {
|
||||
match lop {
|
||||
LengthOrPercentage::Length(px) => self.set_px(px.px()),
|
||||
LengthOrPercentage::Percentage(pc) => self.set_percentage(pc.0),
|
||||
LengthOrPercentage::Calc(calc) => bindings::Gecko_CSSValue_SetCalc(self, calc.into()),
|
||||
/// Sets LengthPercentage value to this nsCSSValue.
|
||||
pub unsafe fn set_length_percentage(&mut self, lp: LengthPercentage) {
|
||||
if lp.was_calc {
|
||||
return bindings::Gecko_CSSValue_SetCalc(self, lp.into())
|
||||
}
|
||||
debug_assert!(lp.percentage.is_none() || lp.unclamped_length() == Length::zero());
|
||||
if let Some(p) = lp.percentage {
|
||||
return self.set_percentage(p.0);
|
||||
}
|
||||
self.set_px(lp.unclamped_length().px());
|
||||
}
|
||||
|
||||
/// Sets a px value to this nsCSSValue.
|
||||
|
@ -86,17 +89,20 @@ impl nsCSSValue {
|
|||
bindings::Gecko_CSSValue_SetPercentage(self, unit_value)
|
||||
}
|
||||
|
||||
/// Returns LengthOrPercentage value.
|
||||
pub unsafe fn get_lop(&self) -> LengthOrPercentage {
|
||||
/// Returns LengthPercentage value.
|
||||
pub unsafe fn get_length_percentage(&self) -> LengthPercentage {
|
||||
match self.mUnit {
|
||||
nsCSSUnit::eCSSUnit_Pixel => {
|
||||
LengthOrPercentage::Length(Length::new(bindings::Gecko_CSSValue_GetNumber(self)))
|
||||
LengthPercentage::new(
|
||||
Length::new(bindings::Gecko_CSSValue_GetNumber(self)),
|
||||
None,
|
||||
)
|
||||
},
|
||||
nsCSSUnit::eCSSUnit_Percent => LengthOrPercentage::Percentage(Percentage(
|
||||
nsCSSUnit::eCSSUnit_Percent => LengthPercentage::new_percent(Percentage(
|
||||
bindings::Gecko_CSSValue_GetPercentage(self),
|
||||
)),
|
||||
nsCSSUnit::eCSSUnit_Calc => {
|
||||
LengthOrPercentage::Calc(bindings::Gecko_CSSValue_GetCalc(self).into())
|
||||
bindings::Gecko_CSSValue_GetCalc(self).into()
|
||||
},
|
||||
_ => panic!("Unexpected unit"),
|
||||
}
|
||||
|
|
|
@ -510,7 +510,7 @@ def set_gecko_property(ffi_name, expr):
|
|||
// set on mContextFlags, and the length field is set to the initial value.
|
||||
|
||||
pub fn set_${ident}(&mut self, v: longhands::${ident}::computed_value::T) {
|
||||
use crate::values::generics::svg::{SVGLength, SvgLengthOrPercentageOrNumber};
|
||||
use crate::values::generics::svg::{SVGLength, SvgLengthPercentageOrNumber};
|
||||
use crate::gecko_bindings::structs::nsStyleSVG_${ident.upper()}_CONTEXT as CONTEXT_VALUE;
|
||||
let length = match v {
|
||||
SVGLength::Length(length) => {
|
||||
|
@ -526,9 +526,9 @@ def set_gecko_property(ffi_name, expr):
|
|||
}
|
||||
};
|
||||
match length {
|
||||
SvgLengthOrPercentageOrNumber::LengthOrPercentage(lop) =>
|
||||
self.gecko.${gecko_ffi_name}.set(lop),
|
||||
SvgLengthOrPercentageOrNumber::Number(num) =>
|
||||
SvgLengthPercentageOrNumber::LengthPercentage(lp) =>
|
||||
self.gecko.${gecko_ffi_name}.set(lp),
|
||||
SvgLengthPercentageOrNumber::Number(num) =>
|
||||
self.gecko.${gecko_ffi_name}.set_value(CoordDataValue::Factor(num.into())),
|
||||
}
|
||||
}
|
||||
|
@ -546,30 +546,28 @@ def set_gecko_property(ffi_name, expr):
|
|||
}
|
||||
|
||||
pub fn clone_${ident}(&self) -> longhands::${ident}::computed_value::T {
|
||||
use crate::values::generics::svg::{SVGLength, SvgLengthOrPercentageOrNumber};
|
||||
use crate::values::computed::LengthOrPercentage;
|
||||
use crate::values::generics::svg::{SVGLength, SvgLengthPercentageOrNumber};
|
||||
use crate::values::computed::LengthPercentage;
|
||||
use crate::gecko_bindings::structs::nsStyleSVG_${ident.upper()}_CONTEXT as CONTEXT_VALUE;
|
||||
if (self.gecko.mContextFlags & CONTEXT_VALUE) != 0 {
|
||||
return SVGLength::ContextValue;
|
||||
}
|
||||
let length = match self.gecko.${gecko_ffi_name}.as_value() {
|
||||
CoordDataValue::Factor(number) => {
|
||||
SvgLengthOrPercentageOrNumber::Number(number)
|
||||
SvgLengthPercentageOrNumber::Number(number)
|
||||
},
|
||||
CoordDataValue::Coord(coord) => {
|
||||
SvgLengthOrPercentageOrNumber::LengthOrPercentage(
|
||||
LengthOrPercentage::Length(Au(coord).into())
|
||||
SvgLengthPercentageOrNumber::LengthPercentage(
|
||||
LengthPercentage::new(Au(coord).into(), None)
|
||||
)
|
||||
},
|
||||
CoordDataValue::Percent(p) => {
|
||||
SvgLengthOrPercentageOrNumber::LengthOrPercentage(
|
||||
LengthOrPercentage::Percentage(Percentage(p))
|
||||
SvgLengthPercentageOrNumber::LengthPercentage(
|
||||
LengthPercentage::new(Au(0).into(), Some(Percentage(p)))
|
||||
)
|
||||
},
|
||||
CoordDataValue::Calc(calc) => {
|
||||
SvgLengthOrPercentageOrNumber::LengthOrPercentage(
|
||||
LengthOrPercentage::Calc(calc.into())
|
||||
)
|
||||
SvgLengthPercentageOrNumber::LengthPercentage(calc.into())
|
||||
},
|
||||
_ => unreachable!("Unexpected coordinate in ${ident}"),
|
||||
};
|
||||
|
@ -941,10 +939,10 @@ def set_gecko_property(ffi_name, expr):
|
|||
transform_functions = [
|
||||
("Matrix3D", "matrix3d", ["number"] * 16),
|
||||
("Matrix", "matrix", ["number"] * 6),
|
||||
("Translate", "translate", ["lop", "optional_lop"]),
|
||||
("Translate3D", "translate3d", ["lop", "lop", "length"]),
|
||||
("TranslateX", "translatex", ["lop"]),
|
||||
("TranslateY", "translatey", ["lop"]),
|
||||
("Translate", "translate", ["lp", "optional_lp"]),
|
||||
("Translate3D", "translate3d", ["lp", "lp", "length"]),
|
||||
("TranslateX", "translatex", ["lp"]),
|
||||
("TranslateY", "translatey", ["lp"]),
|
||||
("TranslateZ", "translatez", ["length"]),
|
||||
("Scale3D", "scale3d", ["number"] * 3),
|
||||
("Scale", "scale", ["number", "optional_number"]),
|
||||
|
@ -995,7 +993,7 @@ transform_functions = [
|
|||
# Note: This is an integer type, but we use it as a percentage value in Gecko, so
|
||||
# need to cast it to f32.
|
||||
"integer_to_percentage" : "bindings::Gecko_CSSValue_SetPercentage(%s, %s as f32)",
|
||||
"lop" : "%s.set_lop(%s)",
|
||||
"lp" : "%s.set_length_percentage(%s)",
|
||||
"angle" : "%s.set_angle(%s)",
|
||||
"number" : "bindings::Gecko_CSSValue_SetNumber(%s, %s)",
|
||||
# Note: We use nsCSSValueSharedList here, instead of nsCSSValueList_heap
|
||||
|
@ -1044,8 +1042,8 @@ transform_functions = [
|
|||
# %s is substituted with the call to GetArrayItem.
|
||||
css_value_getters = {
|
||||
"length" : "Length::new(bindings::Gecko_CSSValue_GetNumber(%s))",
|
||||
"lop" : "%s.get_lop()",
|
||||
"lopon" : "Either::Second(%s.get_lop())",
|
||||
"lp" : "%s.get_length_percentage()",
|
||||
"lpon" : "Either::Second(%s.get_length_percentage())",
|
||||
"lon" : "Either::First(%s.get_length())",
|
||||
"angle" : "%s.get_angle()",
|
||||
"number" : "bindings::Gecko_CSSValue_GetNumber(%s)",
|
||||
|
@ -1271,12 +1269,12 @@ pub fn clone_transform_from_list(
|
|||
|
||||
#[allow(non_snake_case)]
|
||||
pub fn clone_${ident}(&self) -> values::computed::TransformOrigin {
|
||||
use crate::values::computed::{Length, LengthOrPercentage, TransformOrigin};
|
||||
use crate::values::computed::{Length, LengthPercentage, TransformOrigin};
|
||||
TransformOrigin {
|
||||
horizontal: LengthOrPercentage::from_gecko_style_coord(&self.gecko.${gecko_ffi_name}[0])
|
||||
.expect("clone for LengthOrPercentage failed"),
|
||||
vertical: LengthOrPercentage::from_gecko_style_coord(&self.gecko.${gecko_ffi_name}[1])
|
||||
.expect("clone for LengthOrPercentage failed"),
|
||||
horizontal: LengthPercentage::from_gecko_style_coord(&self.gecko.${gecko_ffi_name}[0])
|
||||
.expect("clone for LengthPercentage failed"),
|
||||
vertical: LengthPercentage::from_gecko_style_coord(&self.gecko.${gecko_ffi_name}[1])
|
||||
.expect("clone for LengthPercentage failed"),
|
||||
depth: if let Some(third) = self.gecko.${gecko_ffi_name}.get(2) {
|
||||
Length::from_gecko_style_coord(third)
|
||||
.expect("clone for Length failed")
|
||||
|
@ -1404,19 +1402,19 @@ impl Clone for ${style_struct.gecko_struct_name} {
|
|||
"length::LengthOrAuto": impl_style_coord,
|
||||
"length::LengthOrNormal": impl_style_coord,
|
||||
"length::NonNegativeLengthOrAuto": impl_style_coord,
|
||||
"length::NonNegativeLengthOrPercentageOrNormal": impl_style_coord,
|
||||
"length::NonNegativeLengthPercentageOrNormal": impl_style_coord,
|
||||
"FillRule": impl_simple,
|
||||
"FlexBasis": impl_style_coord,
|
||||
"Length": impl_absolute_length,
|
||||
"LengthOrNormal": impl_style_coord,
|
||||
"LengthOrPercentage": impl_style_coord,
|
||||
"LengthOrPercentageOrAuto": impl_style_coord,
|
||||
"LengthOrPercentageOrNone": impl_style_coord,
|
||||
"LengthPercentage": impl_style_coord,
|
||||
"LengthPercentageOrAuto": impl_style_coord,
|
||||
"LengthPercentageOrNone": impl_style_coord,
|
||||
"MaxLength": impl_style_coord,
|
||||
"MozLength": impl_style_coord,
|
||||
"MozScriptMinSize": impl_absolute_length,
|
||||
"MozScriptSizeMultiplier": impl_simple,
|
||||
"NonNegativeLengthOrPercentage": impl_style_coord,
|
||||
"NonNegativeLengthPercentage": impl_style_coord,
|
||||
"NonNegativeNumber": impl_simple,
|
||||
"Number": impl_simple,
|
||||
"Opacity": impl_simple,
|
||||
|
@ -3086,7 +3084,7 @@ fn static_assert() {
|
|||
}
|
||||
|
||||
pub fn clone_vertical_align(&self) -> longhands::vertical_align::computed_value::T {
|
||||
use crate::values::computed::LengthOrPercentage;
|
||||
use crate::values::computed::LengthPercentage;
|
||||
use crate::values::generics::box_::VerticalAlign;
|
||||
|
||||
let gecko = &self.gecko.mVerticalAlign;
|
||||
|
@ -3094,7 +3092,7 @@ fn static_assert() {
|
|||
CoordDataValue::Enumerated(value) => VerticalAlign::from_gecko_keyword(value),
|
||||
_ => {
|
||||
VerticalAlign::Length(
|
||||
LengthOrPercentage::from_gecko_style_coord(gecko).expect(
|
||||
LengthPercentage::from_gecko_style_coord(gecko).expect(
|
||||
"expected <length-percentage> for vertical-align",
|
||||
),
|
||||
)
|
||||
|
@ -3388,11 +3386,11 @@ fn static_assert() {
|
|||
|
||||
pub fn clone_perspective_origin(&self) -> longhands::perspective_origin::computed_value::T {
|
||||
use crate::properties::longhands::perspective_origin::computed_value::T;
|
||||
use crate::values::computed::LengthOrPercentage;
|
||||
use crate::values::computed::LengthPercentage;
|
||||
T {
|
||||
horizontal: LengthOrPercentage::from_gecko_style_coord(&self.gecko.mPerspectiveOrigin[0])
|
||||
horizontal: LengthPercentage::from_gecko_style_coord(&self.gecko.mPerspectiveOrigin[0])
|
||||
.expect("Expected length or percentage for horizontal value of perspective-origin"),
|
||||
vertical: LengthOrPercentage::from_gecko_style_coord(&self.gecko.mPerspectiveOrigin[1])
|
||||
vertical: LengthPercentage::from_gecko_style_coord(&self.gecko.mPerspectiveOrigin[1])
|
||||
.expect("Expected length or percentage for vertical value of perspective-origin"),
|
||||
}
|
||||
}
|
||||
|
@ -3881,12 +3879,12 @@ fn static_assert() {
|
|||
pub fn clone_${shorthand}_size(&self) -> longhands::${shorthand}_size::computed_value::T {
|
||||
use crate::gecko_bindings::structs::nsStyleCoord_CalcValue as CalcValue;
|
||||
use crate::gecko_bindings::structs::nsStyleImageLayers_Size_DimensionType as DimensionType;
|
||||
use crate::values::computed::NonNegativeLengthOrPercentageOrAuto;
|
||||
use crate::values::computed::NonNegativeLengthPercentageOrAuto;
|
||||
use crate::values::generics::background::BackgroundSize;
|
||||
|
||||
fn to_servo(value: CalcValue, ty: u8) -> NonNegativeLengthOrPercentageOrAuto {
|
||||
fn to_servo(value: CalcValue, ty: u8) -> NonNegativeLengthPercentageOrAuto {
|
||||
if ty == DimensionType::eAuto as u8 {
|
||||
NonNegativeLengthOrPercentageOrAuto::auto()
|
||||
NonNegativeLengthPercentageOrAuto::auto()
|
||||
} else {
|
||||
debug_assert_eq!(ty, DimensionType::eLengthPercentage as u8);
|
||||
value.into()
|
||||
|
@ -4570,14 +4568,14 @@ fn static_assert() {
|
|||
pub fn set_word_spacing(&mut self, v: longhands::word_spacing::computed_value::T) {
|
||||
use crate::values::generics::text::Spacing;
|
||||
match v {
|
||||
Spacing::Value(lop) => self.gecko.mWordSpacing.set(lop),
|
||||
Spacing::Value(lp) => self.gecko.mWordSpacing.set(lp),
|
||||
// https://drafts.csswg.org/css-text-3/#valdef-word-spacing-normal
|
||||
Spacing::Normal => self.gecko.mWordSpacing.set_value(CoordDataValue::Coord(0)),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn clone_word_spacing(&self) -> longhands::word_spacing::computed_value::T {
|
||||
use crate::values::computed::LengthOrPercentage;
|
||||
use crate::values::computed::LengthPercentage;
|
||||
use crate::values::generics::text::Spacing;
|
||||
debug_assert!(
|
||||
matches!(self.gecko.mWordSpacing.as_value(),
|
||||
|
@ -4586,7 +4584,7 @@ fn static_assert() {
|
|||
CoordDataValue::Percent(_) |
|
||||
CoordDataValue::Calc(_)),
|
||||
"Unexpected computed value for word-spacing");
|
||||
LengthOrPercentage::from_gecko_style_coord(&self.gecko.mWordSpacing).map_or(Spacing::Normal, Spacing::Value)
|
||||
LengthPercentage::from_gecko_style_coord(&self.gecko.mWordSpacing).map_or(Spacing::Normal, Spacing::Value)
|
||||
}
|
||||
|
||||
<%call expr="impl_coord_copy('word_spacing', 'mWordSpacing')"></%call>
|
||||
|
@ -5018,7 +5016,7 @@ clip-path
|
|||
|
||||
pub fn set_stroke_dasharray(&mut self, v: longhands::stroke_dasharray::computed_value::T) {
|
||||
use crate::gecko_bindings::structs::nsStyleSVG_STROKE_DASHARRAY_CONTEXT as CONTEXT_VALUE;
|
||||
use crate::values::generics::svg::{SVGStrokeDashArray, SvgLengthOrPercentageOrNumber};
|
||||
use crate::values::generics::svg::{SVGStrokeDashArray, SvgLengthPercentageOrNumber};
|
||||
|
||||
match v {
|
||||
SVGStrokeDashArray::Values(v) => {
|
||||
|
@ -5029,9 +5027,9 @@ clip-path
|
|||
}
|
||||
for (gecko, servo) in self.gecko.mStrokeDasharray.iter_mut().zip(v) {
|
||||
match servo {
|
||||
SvgLengthOrPercentageOrNumber::LengthOrPercentage(lop) =>
|
||||
gecko.set(lop),
|
||||
SvgLengthOrPercentageOrNumber::Number(num) =>
|
||||
SvgLengthPercentageOrNumber::LengthPercentage(lp) =>
|
||||
gecko.set(lp),
|
||||
SvgLengthPercentageOrNumber::Number(num) =>
|
||||
gecko.set_value(CoordDataValue::Factor(num.into())),
|
||||
}
|
||||
}
|
||||
|
@ -5061,8 +5059,9 @@ clip-path
|
|||
|
||||
pub fn clone_stroke_dasharray(&self) -> longhands::stroke_dasharray::computed_value::T {
|
||||
use crate::gecko_bindings::structs::nsStyleSVG_STROKE_DASHARRAY_CONTEXT as CONTEXT_VALUE;
|
||||
use crate::values::computed::LengthOrPercentage;
|
||||
use crate::values::generics::svg::{SVGStrokeDashArray, SvgLengthOrPercentageOrNumber};
|
||||
use crate::values::computed::LengthPercentage;
|
||||
use crate::values::generics::NonNegative;
|
||||
use crate::values::generics::svg::{SVGStrokeDashArray, SvgLengthPercentageOrNumber};
|
||||
|
||||
if self.gecko.mContextFlags & CONTEXT_VALUE != 0 {
|
||||
debug_assert_eq!(self.gecko.mStrokeDasharray.len(), 0);
|
||||
|
@ -5072,16 +5071,16 @@ clip-path
|
|||
for gecko in self.gecko.mStrokeDasharray.iter() {
|
||||
match gecko.as_value() {
|
||||
CoordDataValue::Factor(number) =>
|
||||
vec.push(SvgLengthOrPercentageOrNumber::Number(number.into())),
|
||||
vec.push(SvgLengthPercentageOrNumber::Number(number.into())),
|
||||
CoordDataValue::Coord(coord) =>
|
||||
vec.push(SvgLengthOrPercentageOrNumber::LengthOrPercentage(
|
||||
LengthOrPercentage::Length(Au(coord).into()).into())),
|
||||
vec.push(SvgLengthPercentageOrNumber::LengthPercentage(
|
||||
NonNegative(LengthPercentage::new(Au(coord).into(), None).into()))),
|
||||
CoordDataValue::Percent(p) =>
|
||||
vec.push(SvgLengthOrPercentageOrNumber::LengthOrPercentage(
|
||||
LengthOrPercentage::Percentage(Percentage(p)).into())),
|
||||
vec.push(SvgLengthPercentageOrNumber::LengthPercentage(
|
||||
NonNegative(LengthPercentage::new_percent(Percentage(p)).into()))),
|
||||
CoordDataValue::Calc(calc) =>
|
||||
vec.push(SvgLengthOrPercentageOrNumber::LengthOrPercentage(
|
||||
LengthOrPercentage::Calc(calc.into()).into())),
|
||||
vec.push(SvgLengthPercentageOrNumber::LengthPercentage(
|
||||
NonNegative(LengthPercentage::from(calc).clamp_to_non_negative()))),
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
|
|
|
@ -35,7 +35,7 @@ ${helpers.predefined_type(
|
|||
${helpers.predefined_type(
|
||||
"background-position-" + axis,
|
||||
"position::" + direction + "Position",
|
||||
initial_value="computed::LengthOrPercentage::zero()",
|
||||
initial_value="computed::LengthPercentage::zero()",
|
||||
initial_specified_value="SpecifiedValue::initial_specified_value()",
|
||||
spec="https://drafts.csswg.org/css-backgrounds-4/#propdef-background-position-" + axis,
|
||||
animation_value_type="ComputedValue",
|
||||
|
|
|
@ -69,7 +69,7 @@ ${helpers.gecko_keyword_conversion(
|
|||
type="crate::values::specified::BorderStyle",
|
||||
)}
|
||||
|
||||
// FIXME(#4126): when gfx supports painting it, make this Size2D<LengthOrPercentage>
|
||||
// FIXME(#4126): when gfx supports painting it, make this Size2D<LengthPercentage>
|
||||
% for corner in ["top-left", "top-right", "bottom-right", "bottom-left"]:
|
||||
${helpers.predefined_type(
|
||||
"border-" + corner + "-radius",
|
||||
|
@ -189,7 +189,7 @@ impl crate::values::computed::BorderImageWidth {
|
|||
use crate::gecko_bindings::structs::nsStyleUnit::{eStyleUnit_Factor, eStyleUnit_Auto};
|
||||
use crate::gecko_bindings::sugar::ns_style_coord::CoordData;
|
||||
use crate::gecko::values::GeckoStyleCoordConvertible;
|
||||
use crate::values::computed::{LengthOrPercentage, Number};
|
||||
use crate::values::computed::{LengthPercentage, Number};
|
||||
use crate::values::generics::border::BorderImageSideWidth;
|
||||
use crate::values::generics::NonNegative;
|
||||
|
||||
|
@ -207,8 +207,8 @@ impl crate::values::computed::BorderImageWidth {
|
|||
},
|
||||
_ => {
|
||||
BorderImageSideWidth::Length(
|
||||
NonNegative(LengthOrPercentage::from_gecko_style_coord(&sides.data_at(${i}))
|
||||
.expect("sides[${i}] could not convert to LengthOrPercentage")))
|
||||
NonNegative(LengthPercentage::from_gecko_style_coord(&sides.data_at(${i}))
|
||||
.expect("sides[${i}] could not convert to LengthPercentage")))
|
||||
},
|
||||
},
|
||||
% endfor
|
||||
|
|
|
@ -611,10 +611,10 @@ ${helpers.predefined_type(
|
|||
|
||||
${helpers.predefined_type(
|
||||
"shape-margin",
|
||||
"NonNegativeLengthOrPercentage",
|
||||
"computed::NonNegativeLengthOrPercentage::zero()",
|
||||
"NonNegativeLengthPercentage",
|
||||
"computed::NonNegativeLengthPercentage::zero()",
|
||||
products="gecko",
|
||||
animation_value_type="NonNegativeLengthOrPercentage",
|
||||
animation_value_type="NonNegativeLengthPercentage",
|
||||
flags="APPLIES_TO_FIRST_LETTER",
|
||||
spec="https://drafts.csswg.org/css-shapes/#shape-margin-property",
|
||||
)}
|
||||
|
|
|
@ -53,8 +53,8 @@ ${helpers.single_keyword(
|
|||
|
||||
${helpers.predefined_type(
|
||||
"text-indent",
|
||||
"LengthOrPercentage",
|
||||
"computed::LengthOrPercentage::Length(computed::Length::new(0.))",
|
||||
"LengthPercentage",
|
||||
"computed::LengthPercentage::zero()",
|
||||
animation_value_type="ComputedValue",
|
||||
spec="https://drafts.csswg.org/css-text/#propdef-text-indent",
|
||||
allow_quirks=True,
|
||||
|
|
|
@ -14,8 +14,8 @@
|
|||
%>
|
||||
${helpers.predefined_type(
|
||||
"margin-%s" % side[0],
|
||||
"LengthOrPercentageOrAuto",
|
||||
"computed::LengthOrPercentageOrAuto::Length(computed::Length::new(0.))",
|
||||
"LengthPercentageOrAuto",
|
||||
"computed::LengthPercentageOrAuto::zero()",
|
||||
alias=maybe_moz_logical_alias(product, side, "-moz-margin-%s"),
|
||||
allow_quirks=not side[1],
|
||||
animation_value_type="ComputedValue",
|
||||
|
|
|
@ -16,10 +16,10 @@
|
|||
%>
|
||||
${helpers.predefined_type(
|
||||
"padding-%s" % side[0],
|
||||
"NonNegativeLengthOrPercentage",
|
||||
"computed::NonNegativeLengthOrPercentage::zero()",
|
||||
"NonNegativeLengthPercentage",
|
||||
"computed::NonNegativeLengthPercentage::zero()",
|
||||
alias=maybe_moz_logical_alias(product, side, "-moz-padding-%s"),
|
||||
animation_value_type="NonNegativeLengthOrPercentage",
|
||||
animation_value_type="NonNegativeLengthPercentage",
|
||||
logical=side[1],
|
||||
logical_group="padding",
|
||||
spec=spec,
|
||||
|
|
|
@ -12,8 +12,8 @@
|
|||
% for side in PHYSICAL_SIDES:
|
||||
${helpers.predefined_type(
|
||||
side,
|
||||
"LengthOrPercentageOrAuto",
|
||||
"computed::LengthOrPercentageOrAuto::Auto",
|
||||
"LengthPercentageOrAuto",
|
||||
"computed::LengthPercentageOrAuto::Auto",
|
||||
spec="https://www.w3.org/TR/CSS2/visuren.html#propdef-%s" % side,
|
||||
flags="GETCS_NEEDS_LAYOUT_FLUSH",
|
||||
animation_value_type="ComputedValue",
|
||||
|
@ -26,8 +26,8 @@
|
|||
% for side in LOGICAL_SIDES:
|
||||
${helpers.predefined_type(
|
||||
"inset-%s" % side,
|
||||
"LengthOrPercentageOrAuto",
|
||||
"computed::LengthOrPercentageOrAuto::Auto",
|
||||
"LengthPercentageOrAuto",
|
||||
"computed::LengthPercentageOrAuto::Auto",
|
||||
spec="https://drafts.csswg.org/css-logical-props/#propdef-inset-%s" % side,
|
||||
flags="GETCS_NEEDS_LAYOUT_FLUSH",
|
||||
alias="offset-%s:layout.css.offset-logical-properties.enabled" % side,
|
||||
|
@ -285,8 +285,8 @@ ${helpers.predefined_type(
|
|||
// servo versions (no keyword support)
|
||||
${helpers.predefined_type(
|
||||
size,
|
||||
"LengthOrPercentageOrAuto",
|
||||
"computed::LengthOrPercentageOrAuto::Auto",
|
||||
"LengthPercentageOrAuto",
|
||||
"computed::LengthPercentageOrAuto::Auto",
|
||||
"parse_non_negative",
|
||||
spec=spec % size,
|
||||
logical_group="size",
|
||||
|
@ -296,8 +296,8 @@ ${helpers.predefined_type(
|
|||
)}
|
||||
${helpers.predefined_type(
|
||||
"min-%s" % size,
|
||||
"LengthOrPercentage",
|
||||
"computed::LengthOrPercentage::Length(computed::Length::new(0.))",
|
||||
"LengthPercentage",
|
||||
"computed::LengthPercentage::Length(computed::Length::new(0.))",
|
||||
"parse_non_negative",
|
||||
spec=spec % ("min-%s" % size),
|
||||
logical_group="min-size",
|
||||
|
@ -308,8 +308,8 @@ ${helpers.predefined_type(
|
|||
)}
|
||||
${helpers.predefined_type(
|
||||
"max-%s" % size,
|
||||
"LengthOrPercentageOrNone",
|
||||
"computed::LengthOrPercentageOrNone::None",
|
||||
"LengthPercentageOrNone",
|
||||
"computed::LengthPercentageOrNone::None",
|
||||
"parse_non_negative",
|
||||
spec=spec % ("max-%s" % size),
|
||||
logical_group="max-size",
|
||||
|
@ -408,24 +408,24 @@ ${helpers.predefined_type(
|
|||
|
||||
${helpers.predefined_type(
|
||||
"column-gap",
|
||||
"length::NonNegativeLengthOrPercentageOrNormal",
|
||||
"length::NonNegativeLengthPercentageOrNormal",
|
||||
"Either::Second(Normal)",
|
||||
alias="grid-column-gap" if product == "gecko" else "",
|
||||
extra_prefixes="moz",
|
||||
servo_pref="layout.columns.enabled",
|
||||
spec="https://drafts.csswg.org/css-align-3/#propdef-column-gap",
|
||||
animation_value_type="NonNegativeLengthOrPercentageOrNormal",
|
||||
animation_value_type="NonNegativeLengthPercentageOrNormal",
|
||||
servo_restyle_damage="reflow",
|
||||
)}
|
||||
|
||||
// no need for -moz- prefixed alias for this property
|
||||
${helpers.predefined_type(
|
||||
"row-gap",
|
||||
"length::NonNegativeLengthOrPercentageOrNormal",
|
||||
"length::NonNegativeLengthPercentageOrNormal",
|
||||
"Either::Second(Normal)",
|
||||
alias="grid-row-gap",
|
||||
products="gecko",
|
||||
spec="https://drafts.csswg.org/css-align-3/#propdef-row-gap",
|
||||
animation_value_type="NonNegativeLengthOrPercentageOrNormal",
|
||||
animation_value_type="NonNegativeLengthPercentageOrNormal",
|
||||
servo_restyle_damage="reflow",
|
||||
)}
|
||||
|
|
|
@ -118,7 +118,7 @@ ${helpers.predefined_type(
|
|||
${helpers.predefined_type(
|
||||
"mask-position-" + axis,
|
||||
"position::" + direction + "Position",
|
||||
"computed::LengthOrPercentage::zero()",
|
||||
"computed::LengthPercentage::zero()",
|
||||
products="gecko",
|
||||
extra_prefixes="webkit",
|
||||
initial_specified_value="specified::PositionComponent::Center",
|
||||
|
|
|
@ -3017,7 +3017,7 @@ impl ComputedValuesInner {
|
|||
|
||||
/// Get the logical computed inline size.
|
||||
#[inline]
|
||||
pub fn content_inline_size(&self) -> computed::LengthOrPercentageOrAuto {
|
||||
pub fn content_inline_size(&self) -> computed::LengthPercentageOrAuto {
|
||||
let position_style = self.get_position();
|
||||
if self.writing_mode.is_vertical() {
|
||||
position_style.height
|
||||
|
@ -3028,42 +3028,42 @@ impl ComputedValuesInner {
|
|||
|
||||
/// Get the logical computed block size.
|
||||
#[inline]
|
||||
pub fn content_block_size(&self) -> computed::LengthOrPercentageOrAuto {
|
||||
pub fn content_block_size(&self) -> computed::LengthPercentageOrAuto {
|
||||
let position_style = self.get_position();
|
||||
if self.writing_mode.is_vertical() { position_style.width } else { position_style.height }
|
||||
}
|
||||
|
||||
/// Get the logical computed min inline size.
|
||||
#[inline]
|
||||
pub fn min_inline_size(&self) -> computed::LengthOrPercentage {
|
||||
pub fn min_inline_size(&self) -> computed::LengthPercentage {
|
||||
let position_style = self.get_position();
|
||||
if self.writing_mode.is_vertical() { position_style.min_height } else { position_style.min_width }
|
||||
}
|
||||
|
||||
/// Get the logical computed min block size.
|
||||
#[inline]
|
||||
pub fn min_block_size(&self) -> computed::LengthOrPercentage {
|
||||
pub fn min_block_size(&self) -> computed::LengthPercentage {
|
||||
let position_style = self.get_position();
|
||||
if self.writing_mode.is_vertical() { position_style.min_width } else { position_style.min_height }
|
||||
}
|
||||
|
||||
/// Get the logical computed max inline size.
|
||||
#[inline]
|
||||
pub fn max_inline_size(&self) -> computed::LengthOrPercentageOrNone {
|
||||
pub fn max_inline_size(&self) -> computed::LengthPercentageOrNone {
|
||||
let position_style = self.get_position();
|
||||
if self.writing_mode.is_vertical() { position_style.max_height } else { position_style.max_width }
|
||||
}
|
||||
|
||||
/// Get the logical computed max block size.
|
||||
#[inline]
|
||||
pub fn max_block_size(&self) -> computed::LengthOrPercentageOrNone {
|
||||
pub fn max_block_size(&self) -> computed::LengthPercentageOrNone {
|
||||
let position_style = self.get_position();
|
||||
if self.writing_mode.is_vertical() { position_style.max_width } else { position_style.max_height }
|
||||
}
|
||||
|
||||
/// Get the logical computed padding for this writing mode.
|
||||
#[inline]
|
||||
pub fn logical_padding(&self) -> LogicalMargin<computed::LengthOrPercentage> {
|
||||
pub fn logical_padding(&self) -> LogicalMargin<computed::LengthPercentage> {
|
||||
let padding_style = self.get_padding();
|
||||
LogicalMargin::from_physical(self.writing_mode, SideOffsets2D::new(
|
||||
padding_style.padding_top.0,
|
||||
|
@ -3093,7 +3093,7 @@ impl ComputedValuesInner {
|
|||
|
||||
/// Gets the logical computed margin from this style.
|
||||
#[inline]
|
||||
pub fn logical_margin(&self) -> LogicalMargin<computed::LengthOrPercentageOrAuto> {
|
||||
pub fn logical_margin(&self) -> LogicalMargin<computed::LengthPercentageOrAuto> {
|
||||
let margin_style = self.get_margin();
|
||||
LogicalMargin::from_physical(self.writing_mode, SideOffsets2D::new(
|
||||
margin_style.margin_top,
|
||||
|
@ -3105,7 +3105,7 @@ impl ComputedValuesInner {
|
|||
|
||||
/// Gets the logical position from this style.
|
||||
#[inline]
|
||||
pub fn logical_position(&self) -> LogicalMargin<computed::LengthOrPercentageOrAuto> {
|
||||
pub fn logical_position(&self) -> LogicalMargin<computed::LengthPercentageOrAuto> {
|
||||
// FIXME(SimonSapin): should be the writing mode of the containing block, maybe?
|
||||
let position_style = self.get_position();
|
||||
LogicalMargin::from_physical(self.writing_mode, SideOffsets2D::new(
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
|
||||
<%namespace name="helpers" file="/helpers.mako.rs" />
|
||||
|
||||
${helpers.four_sides_shorthand("margin", "margin-%s", "specified::LengthOrPercentageOrAuto::parse",
|
||||
${helpers.four_sides_shorthand("margin", "margin-%s", "specified::LengthPercentageOrAuto::parse",
|
||||
spec="https://drafts.csswg.org/css-box/#propdef-margin",
|
||||
allowed_in_page_rule=True,
|
||||
allow_quirks=True)}
|
||||
|
|
|
@ -4,6 +4,6 @@
|
|||
|
||||
<%namespace name="helpers" file="/helpers.mako.rs" />
|
||||
|
||||
${helpers.four_sides_shorthand("padding", "padding-%s", "specified::NonNegativeLengthOrPercentage::parse",
|
||||
${helpers.four_sides_shorthand("padding", "padding-%s", "specified::NonNegativeLengthPercentage::parse",
|
||||
spec="https://drafts.csswg.org/css-box-3/#propdef-padding",
|
||||
allow_quirks=True)}
|
||||
|
|
|
@ -18,7 +18,7 @@ use crate::shared_lock::{SharedRwLockReadGuard, StylesheetGuards, ToCssWithGuard
|
|||
use crate::str::CssStringWriter;
|
||||
use crate::stylesheets::{Origin, StylesheetInDocument};
|
||||
use crate::values::computed::{Context, ToComputedValue};
|
||||
use crate::values::specified::{LengthOrPercentageOrAuto, NoCalcLength, ViewportPercentageLength};
|
||||
use crate::values::specified::{self, LengthPercentageOrAuto, NoCalcLength, ViewportPercentageLength};
|
||||
use app_units::Au;
|
||||
use cssparser::CowRcStr;
|
||||
use cssparser::{parse_important, AtRuleParser, DeclarationListParser, DeclarationParser, Parser};
|
||||
|
@ -149,7 +149,7 @@ trait FromMeta: Sized {
|
|||
#[cfg_attr(feature = "servo", derive(MallocSizeOf))]
|
||||
#[derive(Clone, Debug, PartialEq, ToCss)]
|
||||
pub enum ViewportLength {
|
||||
Specified(LengthOrPercentageOrAuto),
|
||||
Specified(LengthPercentageOrAuto),
|
||||
ExtendToZoom,
|
||||
}
|
||||
|
||||
|
@ -157,7 +157,9 @@ impl FromMeta for ViewportLength {
|
|||
fn from_meta(value: &str) -> Option<ViewportLength> {
|
||||
macro_rules! specified {
|
||||
($value:expr) => {
|
||||
ViewportLength::Specified(LengthOrPercentageOrAuto::Length($value))
|
||||
ViewportLength::Specified(LengthPercentageOrAuto::LengthPercentage(
|
||||
specified::LengthPercentage::Length($value)
|
||||
))
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -184,7 +186,7 @@ impl ViewportLength {
|
|||
) -> Result<Self, ParseError<'i>> {
|
||||
// we explicitly do not accept 'extend-to-zoom', since it is a UA
|
||||
// internal value for <META> viewport translation
|
||||
LengthOrPercentageOrAuto::parse_non_negative(context, input).map(ViewportLength::Specified)
|
||||
LengthPercentageOrAuto::parse_non_negative(context, input).map(ViewportLength::Specified)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -466,10 +468,10 @@ impl ViewportRule {
|
|||
if !has_width && has_zoom {
|
||||
if has_height {
|
||||
push_descriptor!(MinWidth(ViewportLength::Specified(
|
||||
LengthOrPercentageOrAuto::Auto
|
||||
LengthPercentageOrAuto::Auto
|
||||
)));
|
||||
push_descriptor!(MaxWidth(ViewportLength::Specified(
|
||||
LengthOrPercentageOrAuto::Auto
|
||||
LengthPercentageOrAuto::Auto
|
||||
)));
|
||||
} else {
|
||||
push_descriptor!(MinWidth(ViewportLength::ExtendToZoom));
|
||||
|
@ -752,16 +754,10 @@ impl MaybeNew for ViewportConstraints {
|
|||
if let Some($value) = $value {
|
||||
match *$value {
|
||||
ViewportLength::Specified(ref length) => match *length {
|
||||
LengthOrPercentageOrAuto::Length(ref value) => {
|
||||
Some(Au::from(value.to_computed_value(&context)))
|
||||
},
|
||||
LengthOrPercentageOrAuto::Percentage(value) => {
|
||||
Some(initial_viewport.$dimension.scale_by(value.0))
|
||||
},
|
||||
LengthOrPercentageOrAuto::Auto => None,
|
||||
LengthOrPercentageOrAuto::Calc(ref calc) => calc
|
||||
LengthPercentageOrAuto::Auto => None,
|
||||
LengthPercentageOrAuto::LengthPercentage(ref lop) => Some(lop
|
||||
.to_computed_value(&context)
|
||||
.to_used_value(Some(initial_viewport.$dimension)),
|
||||
.to_used_value(initial_viewport.$dimension)),
|
||||
},
|
||||
ViewportLength::ExtendToZoom => {
|
||||
// $extend_to will be 'None' if 'extend-to-zoom' is 'auto'
|
||||
|
|
|
@ -4,15 +4,14 @@
|
|||
|
||||
//! Animation implementation for various length-related types.
|
||||
|
||||
use super::{Animate, Procedure, ToAnimatedValue, ToAnimatedZero};
|
||||
use crate::values::computed::length::{CalcLengthOrPercentage, Length};
|
||||
use crate::values::computed::length::{LengthOrPercentageOrAuto, LengthOrPercentageOrNone};
|
||||
use super::{Animate, Procedure, ToAnimatedValue};
|
||||
use crate::values::computed::length::LengthPercentage;
|
||||
use crate::values::computed::MaxLength as ComputedMaxLength;
|
||||
use crate::values::computed::MozLength as ComputedMozLength;
|
||||
use crate::values::computed::Percentage;
|
||||
|
||||
/// <https://drafts.csswg.org/css-transitions/#animtype-lpcalc>
|
||||
impl Animate for CalcLengthOrPercentage {
|
||||
impl Animate for LengthPercentage {
|
||||
#[inline]
|
||||
fn animate(&self, other: &Self, procedure: Procedure) -> Result<Self, ()> {
|
||||
let animate_percentage_half = |this: Option<Percentage>, other: Option<Percentage>| {
|
||||
|
@ -28,42 +27,17 @@ impl Animate for CalcLengthOrPercentage {
|
|||
.unclamped_length()
|
||||
.animate(&other.unclamped_length(), procedure)?;
|
||||
let percentage = animate_percentage_half(self.percentage, other.percentage)?;
|
||||
Ok(CalcLengthOrPercentage::with_clamping_mode(
|
||||
let is_calc = self.was_calc || other.was_calc || self.percentage.is_some() != other.percentage.is_some();
|
||||
Ok(Self::with_clamping_mode(
|
||||
length,
|
||||
percentage,
|
||||
self.clamping_mode,
|
||||
is_calc,
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
impl ToAnimatedZero for LengthOrPercentageOrAuto {
|
||||
#[inline]
|
||||
fn to_animated_zero(&self) -> Result<Self, ()> {
|
||||
match *self {
|
||||
LengthOrPercentageOrAuto::Length(_) |
|
||||
LengthOrPercentageOrAuto::Percentage(_) |
|
||||
LengthOrPercentageOrAuto::Calc(_) => {
|
||||
Ok(LengthOrPercentageOrAuto::Length(Length::new(0.)))
|
||||
},
|
||||
LengthOrPercentageOrAuto::Auto => Err(()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ToAnimatedZero for LengthOrPercentageOrNone {
|
||||
#[inline]
|
||||
fn to_animated_zero(&self) -> Result<Self, ()> {
|
||||
match *self {
|
||||
LengthOrPercentageOrNone::Length(_) |
|
||||
LengthOrPercentageOrNone::Percentage(_) |
|
||||
LengthOrPercentageOrNone::Calc(_) => {
|
||||
Ok(LengthOrPercentageOrNone::Length(Length::new(0.)))
|
||||
},
|
||||
LengthOrPercentageOrNone::None => Err(()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// FIXME(emilio): These should use NonNegative<> instead.
|
||||
impl ToAnimatedValue for ComputedMaxLength {
|
||||
type AnimatedValue = Self;
|
||||
|
||||
|
@ -74,20 +48,17 @@ impl ToAnimatedValue for ComputedMaxLength {
|
|||
|
||||
#[inline]
|
||||
fn from_animated_value(animated: Self::AnimatedValue) -> Self {
|
||||
use crate::values::computed::{Length, LengthOrPercentageOrNone, Percentage};
|
||||
use crate::values::computed::LengthPercentageOrNone;
|
||||
use crate::values::generics::length::MaxLength as GenericMaxLength;
|
||||
match animated {
|
||||
GenericMaxLength::LengthOrPercentageOrNone(lopn) => {
|
||||
let result = match lopn {
|
||||
LengthOrPercentageOrNone::Length(px) => {
|
||||
LengthOrPercentageOrNone::Length(Length::new(px.px().max(0.)))
|
||||
GenericMaxLength::LengthPercentageOrNone(lpn) => {
|
||||
let result = match lpn {
|
||||
LengthPercentageOrNone::LengthPercentage(len) => {
|
||||
LengthPercentageOrNone::LengthPercentage(len.clamp_to_non_negative())
|
||||
},
|
||||
LengthOrPercentageOrNone::Percentage(percentage) => {
|
||||
LengthOrPercentageOrNone::Percentage(Percentage(percentage.0.max(0.)))
|
||||
},
|
||||
_ => lopn,
|
||||
LengthPercentageOrNone::None => lpn,
|
||||
};
|
||||
GenericMaxLength::LengthOrPercentageOrNone(result)
|
||||
GenericMaxLength::LengthPercentageOrNone(result)
|
||||
},
|
||||
_ => animated,
|
||||
}
|
||||
|
@ -104,20 +75,10 @@ impl ToAnimatedValue for ComputedMozLength {
|
|||
|
||||
#[inline]
|
||||
fn from_animated_value(animated: Self::AnimatedValue) -> Self {
|
||||
use crate::values::computed::{Length, LengthOrPercentageOrAuto, Percentage};
|
||||
use crate::values::generics::length::MozLength as GenericMozLength;
|
||||
match animated {
|
||||
GenericMozLength::LengthOrPercentageOrAuto(lopa) => {
|
||||
let result = match lopa {
|
||||
LengthOrPercentageOrAuto::Length(px) => {
|
||||
LengthOrPercentageOrAuto::Length(Length::new(px.px().max(0.)))
|
||||
},
|
||||
LengthOrPercentageOrAuto::Percentage(percentage) => {
|
||||
LengthOrPercentageOrAuto::Percentage(Percentage(percentage.0.max(0.)))
|
||||
},
|
||||
_ => lopa,
|
||||
};
|
||||
GenericMozLength::LengthOrPercentageOrAuto(result)
|
||||
GenericMozLength::LengthPercentageOrAuto(lpa) => {
|
||||
GenericMozLength::LengthPercentageOrAuto(lpa.clamp_to_non_negative())
|
||||
},
|
||||
_ => animated,
|
||||
}
|
||||
|
|
|
@ -9,7 +9,7 @@
|
|||
//! module's raison d'être is to ultimately contain all these types.
|
||||
|
||||
use crate::properties::PropertyId;
|
||||
use crate::values::computed::length::CalcLengthOrPercentage;
|
||||
use crate::values::computed::length::LengthPercentage;
|
||||
use crate::values::computed::url::ComputedUrl;
|
||||
use crate::values::computed::Angle as ComputedAngle;
|
||||
use crate::values::computed::Image;
|
||||
|
@ -335,7 +335,7 @@ macro_rules! trivial_to_animated_value {
|
|||
}
|
||||
|
||||
trivial_to_animated_value!(Au);
|
||||
trivial_to_animated_value!(CalcLengthOrPercentage);
|
||||
trivial_to_animated_value!(LengthPercentage);
|
||||
trivial_to_animated_value!(ComputedAngle);
|
||||
trivial_to_animated_value!(ComputedUrl);
|
||||
trivial_to_animated_value!(bool);
|
||||
|
|
|
@ -8,9 +8,9 @@ use super::{Animate, Procedure, ToAnimatedZero};
|
|||
use crate::properties::animated_properties::ListAnimation;
|
||||
use crate::values::animated::color::Color as AnimatedColor;
|
||||
use crate::values::computed::url::ComputedUrl;
|
||||
use crate::values::computed::{LengthOrPercentage, Number, NumberOrPercentage};
|
||||
use crate::values::computed::{LengthPercentage, Number, NumberOrPercentage};
|
||||
use crate::values::distance::{ComputeSquaredDistance, SquaredDistance};
|
||||
use crate::values::generics::svg::{SVGLength, SVGPaint, SvgLengthOrPercentageOrNumber};
|
||||
use crate::values::generics::svg::{SVGLength, SVGPaint, SvgLengthPercentageOrNumber};
|
||||
use crate::values::generics::svg::{SVGOpacity, SVGStrokeDashArray};
|
||||
|
||||
/// Animated SVGPaint.
|
||||
|
@ -29,19 +29,25 @@ impl ToAnimatedZero for IntermediateSVGPaint {
|
|||
// FIXME: We need to handle calc here properly, see
|
||||
// https://bugzilla.mozilla.org/show_bug.cgi?id=1386967
|
||||
fn to_number_or_percentage(
|
||||
value: &SvgLengthOrPercentageOrNumber<LengthOrPercentage, Number>,
|
||||
value: &SvgLengthPercentageOrNumber<LengthPercentage, Number>,
|
||||
) -> Result<NumberOrPercentage, ()> {
|
||||
Ok(match *value {
|
||||
SvgLengthOrPercentageOrNumber::LengthOrPercentage(ref l) => match *l {
|
||||
LengthOrPercentage::Length(ref l) => NumberOrPercentage::Number(l.px()),
|
||||
LengthOrPercentage::Percentage(ref p) => NumberOrPercentage::Percentage(*p),
|
||||
LengthOrPercentage::Calc(..) => return Err(()),
|
||||
SvgLengthPercentageOrNumber::LengthPercentage(ref l) => {
|
||||
match l.percentage {
|
||||
Some(p) => {
|
||||
if l.unclamped_length().px() != 0. {
|
||||
return Err(());
|
||||
}
|
||||
NumberOrPercentage::Percentage(p)
|
||||
}
|
||||
None => NumberOrPercentage::Number(l.length().px())
|
||||
}
|
||||
},
|
||||
SvgLengthOrPercentageOrNumber::Number(ref n) => NumberOrPercentage::Number(*n),
|
||||
SvgLengthPercentageOrNumber::Number(ref n) => NumberOrPercentage::Number(*n),
|
||||
})
|
||||
}
|
||||
|
||||
impl Animate for SvgLengthOrPercentageOrNumber<LengthOrPercentage, Number> {
|
||||
impl Animate for SvgLengthPercentageOrNumber<LengthPercentage, Number> {
|
||||
#[inline]
|
||||
fn animate(&self, other: &Self, procedure: Procedure) -> Result<Self, ()> {
|
||||
let this = to_number_or_percentage(self)?;
|
||||
|
@ -49,20 +55,20 @@ impl Animate for SvgLengthOrPercentageOrNumber<LengthOrPercentage, Number> {
|
|||
|
||||
match (this, other) {
|
||||
(NumberOrPercentage::Number(ref this), NumberOrPercentage::Number(ref other)) => Ok(
|
||||
SvgLengthOrPercentageOrNumber::Number(this.animate(other, procedure)?),
|
||||
SvgLengthPercentageOrNumber::Number(this.animate(other, procedure)?),
|
||||
),
|
||||
(
|
||||
NumberOrPercentage::Percentage(ref this),
|
||||
NumberOrPercentage::Percentage(ref other),
|
||||
) => Ok(SvgLengthOrPercentageOrNumber::LengthOrPercentage(
|
||||
LengthOrPercentage::Percentage(this.animate(other, procedure)?),
|
||||
) => Ok(SvgLengthPercentageOrNumber::LengthPercentage(
|
||||
LengthPercentage::new_percent(this.animate(other, procedure)?),
|
||||
)),
|
||||
_ => Err(()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ComputeSquaredDistance for SvgLengthOrPercentageOrNumber<LengthOrPercentage, Number> {
|
||||
impl ComputeSquaredDistance for SvgLengthPercentageOrNumber<LengthPercentage, Number> {
|
||||
fn compute_squared_distance(&self, other: &Self) -> Result<SquaredDistance, ()> {
|
||||
to_number_or_percentage(self)?.compute_squared_distance(&to_number_or_percentage(other)?)
|
||||
}
|
||||
|
|
|
@ -16,7 +16,7 @@ use crate::values::computed::transform::TransformOperation as ComputedTransformO
|
|||
use crate::values::computed::transform::Translate as ComputedTranslate;
|
||||
use crate::values::computed::transform::{DirectionVector, Matrix, Matrix3D};
|
||||
use crate::values::computed::Angle;
|
||||
use crate::values::computed::{Length, LengthOrPercentage};
|
||||
use crate::values::computed::{Length, LengthPercentage};
|
||||
use crate::values::computed::{Number, Percentage};
|
||||
use crate::values::distance::{ComputeSquaredDistance, SquaredDistance};
|
||||
use crate::values::generics::transform::{self, Transform, TransformOperation};
|
||||
|
@ -1043,8 +1043,8 @@ impl Animate for ComputedTransformOperation {
|
|||
) => Ok(TransformOperation::Translate(
|
||||
fx.animate(tx, procedure)?,
|
||||
Some(
|
||||
fy.unwrap_or(LengthOrPercentage::zero())
|
||||
.animate(&ty.unwrap_or(LengthOrPercentage::zero()), procedure)?,
|
||||
fy.unwrap_or(LengthPercentage::zero())
|
||||
.animate(&ty.unwrap_or(LengthPercentage::zero()), procedure)?,
|
||||
),
|
||||
)),
|
||||
(&TransformOperation::TranslateX(ref f), &TransformOperation::TranslateX(ref t)) => {
|
||||
|
@ -1167,17 +1167,6 @@ impl Animate for ComputedTransformOperation {
|
|||
// See https://bugzilla.mozilla.org/show_bug.cgi?id=1318591#c0.
|
||||
impl ComputeSquaredDistance for ComputedTransformOperation {
|
||||
fn compute_squared_distance(&self, other: &Self) -> Result<SquaredDistance, ()> {
|
||||
// For translate, We don't want to require doing layout in order to calculate the result, so
|
||||
// drop the percentage part. However, dropping percentage makes us impossible to
|
||||
// compute the distance for the percentage-percentage case, but Gecko uses the
|
||||
// same formula, so it's fine for now.
|
||||
// Note: We use pixel value to compute the distance for translate, so we have to
|
||||
// convert Au into px.
|
||||
let extract_pixel_length = |lop: &LengthOrPercentage| match *lop {
|
||||
LengthOrPercentage::Length(px) => px.px(),
|
||||
LengthOrPercentage::Percentage(_) => 0.,
|
||||
LengthOrPercentage::Calc(calc) => calc.length().px(),
|
||||
};
|
||||
match (self, other) {
|
||||
(&TransformOperation::Matrix3D(ref this), &TransformOperation::Matrix3D(ref other)) => {
|
||||
this.compute_squared_distance(other)
|
||||
|
@ -1199,10 +1188,16 @@ impl ComputeSquaredDistance for ComputedTransformOperation {
|
|||
&TransformOperation::Translate3D(ref fx, ref fy, ref fz),
|
||||
&TransformOperation::Translate3D(ref tx, ref ty, ref tz),
|
||||
) => {
|
||||
let fx = extract_pixel_length(&fx);
|
||||
let fy = extract_pixel_length(&fy);
|
||||
let tx = extract_pixel_length(&tx);
|
||||
let ty = extract_pixel_length(&ty);
|
||||
// For translate, We don't want to require doing layout in order
|
||||
// to calculate the result, so drop the percentage part.
|
||||
//
|
||||
// However, dropping percentage makes us impossible to compute
|
||||
// the distance for the percentage-percentage case, but Gecko
|
||||
// uses the same formula, so it's fine for now.
|
||||
let fx = fx.length_component().px();
|
||||
let fy = fy.length_component().px();
|
||||
let tx = tx.length_component().px();
|
||||
let ty = ty.length_component().px();
|
||||
|
||||
Ok(fx.compute_squared_distance(&tx)? +
|
||||
fy.compute_squared_distance(&ty)? +
|
||||
|
@ -1388,15 +1383,15 @@ impl ComputeSquaredDistance for ComputedRotate {
|
|||
|
||||
/// <https://drafts.csswg.org/css-transforms-2/#propdef-translate>
|
||||
impl ComputedTranslate {
|
||||
fn resolve(&self) -> (LengthOrPercentage, LengthOrPercentage, Length) {
|
||||
fn resolve(&self) -> (LengthPercentage, LengthPercentage, Length) {
|
||||
// According to the spec:
|
||||
// https://drafts.csswg.org/css-transforms-2/#individual-transforms
|
||||
//
|
||||
// Unspecified translations default to 0px
|
||||
match *self {
|
||||
Translate::None => (
|
||||
LengthOrPercentage::zero(),
|
||||
LengthOrPercentage::zero(),
|
||||
LengthPercentage::zero(),
|
||||
LengthPercentage::zero(),
|
||||
Length::zero(),
|
||||
),
|
||||
Translate::Translate3D(tx, ty, tz) => (tx, ty, tz),
|
||||
|
|
|
@ -4,20 +4,20 @@
|
|||
|
||||
//! Computed types for CSS values related to backgrounds.
|
||||
|
||||
use crate::values::computed::length::NonNegativeLengthOrPercentageOrAuto;
|
||||
use crate::values::computed::length::NonNegativeLengthPercentageOrAuto;
|
||||
use crate::values::generics::background::BackgroundSize as GenericBackgroundSize;
|
||||
|
||||
pub use crate::values::specified::background::BackgroundRepeat;
|
||||
|
||||
/// A computed value for the `background-size` property.
|
||||
pub type BackgroundSize = GenericBackgroundSize<NonNegativeLengthOrPercentageOrAuto>;
|
||||
pub type BackgroundSize = GenericBackgroundSize<NonNegativeLengthPercentageOrAuto>;
|
||||
|
||||
impl BackgroundSize {
|
||||
/// Returns `auto auto`.
|
||||
pub fn auto() -> Self {
|
||||
GenericBackgroundSize::Explicit {
|
||||
width: NonNegativeLengthOrPercentageOrAuto::auto(),
|
||||
height: NonNegativeLengthOrPercentageOrAuto::auto(),
|
||||
width: NonNegativeLengthPercentageOrAuto::auto(),
|
||||
height: NonNegativeLengthPercentageOrAuto::auto(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -8,7 +8,7 @@
|
|||
//! [basic-shape]: https://drafts.csswg.org/css-shapes/#typedef-basic-shape
|
||||
|
||||
use crate::values::computed::url::ComputedUrl;
|
||||
use crate::values::computed::{Image, LengthOrPercentage, NonNegativeLengthOrPercentage};
|
||||
use crate::values::computed::{Image, LengthPercentage, NonNegativeLengthPercentage};
|
||||
use crate::values::generics::basic_shape as generic;
|
||||
use std::fmt::{self, Write};
|
||||
use style_traits::{CssWriter, ToCss};
|
||||
|
@ -24,25 +24,25 @@ pub type FloatAreaShape = generic::FloatAreaShape<BasicShape, Image>;
|
|||
|
||||
/// A computed basic shape.
|
||||
pub type BasicShape = generic::BasicShape<
|
||||
LengthOrPercentage,
|
||||
LengthOrPercentage,
|
||||
LengthOrPercentage,
|
||||
NonNegativeLengthOrPercentage,
|
||||
LengthPercentage,
|
||||
LengthPercentage,
|
||||
LengthPercentage,
|
||||
NonNegativeLengthPercentage,
|
||||
>;
|
||||
|
||||
/// The computed value of `inset()`
|
||||
pub type InsetRect = generic::InsetRect<LengthOrPercentage, NonNegativeLengthOrPercentage>;
|
||||
pub type InsetRect = generic::InsetRect<LengthPercentage, NonNegativeLengthPercentage>;
|
||||
|
||||
/// A computed circle.
|
||||
pub type Circle =
|
||||
generic::Circle<LengthOrPercentage, LengthOrPercentage, NonNegativeLengthOrPercentage>;
|
||||
generic::Circle<LengthPercentage, LengthPercentage, NonNegativeLengthPercentage>;
|
||||
|
||||
/// A computed ellipse.
|
||||
pub type Ellipse =
|
||||
generic::Ellipse<LengthOrPercentage, LengthOrPercentage, NonNegativeLengthOrPercentage>;
|
||||
generic::Ellipse<LengthPercentage, LengthPercentage, NonNegativeLengthPercentage>;
|
||||
|
||||
/// The computed value of `ShapeRadius`
|
||||
pub type ShapeRadius = generic::ShapeRadius<NonNegativeLengthOrPercentage>;
|
||||
pub type ShapeRadius = generic::ShapeRadius<NonNegativeLengthPercentage>;
|
||||
|
||||
impl ToCss for Circle {
|
||||
fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
|
||||
//! Computed types for CSS values related to borders.
|
||||
|
||||
use crate::values::computed::length::{NonNegativeLength, NonNegativeLengthOrPercentage};
|
||||
use crate::values::computed::length::{NonNegativeLength, NonNegativeLengthPercentage};
|
||||
use crate::values::computed::{NonNegativeNumber, NonNegativeNumberOrPercentage};
|
||||
use crate::values::generics::border::BorderCornerRadius as GenericBorderCornerRadius;
|
||||
use crate::values::generics::border::BorderImageSideWidth as GenericBorderImageSideWidth;
|
||||
|
@ -23,16 +23,16 @@ pub type BorderImageWidth = Rect<BorderImageSideWidth>;
|
|||
|
||||
/// A computed value for a single side of a `border-image-width` property.
|
||||
pub type BorderImageSideWidth =
|
||||
GenericBorderImageSideWidth<NonNegativeLengthOrPercentage, NonNegativeNumber>;
|
||||
GenericBorderImageSideWidth<NonNegativeLengthPercentage, NonNegativeNumber>;
|
||||
|
||||
/// A computed value for the `border-image-slice` property.
|
||||
pub type BorderImageSlice = GenericBorderImageSlice<NonNegativeNumberOrPercentage>;
|
||||
|
||||
/// A computed value for the `border-radius` property.
|
||||
pub type BorderRadius = GenericBorderRadius<NonNegativeLengthOrPercentage>;
|
||||
pub type BorderRadius = GenericBorderRadius<NonNegativeLengthPercentage>;
|
||||
|
||||
/// A computed value for the `border-*-radius` longhand properties.
|
||||
pub type BorderCornerRadius = GenericBorderCornerRadius<NonNegativeLengthOrPercentage>;
|
||||
pub type BorderCornerRadius = GenericBorderCornerRadius<NonNegativeLengthPercentage>;
|
||||
|
||||
/// A computed value for the `border-spacing` longhand property.
|
||||
pub type BorderSpacing = GenericBorderSpacing<NonNegativeLength>;
|
||||
|
@ -80,8 +80,8 @@ impl BorderCornerRadius {
|
|||
/// Returns `0 0`.
|
||||
pub fn zero() -> Self {
|
||||
GenericBorderCornerRadius(Size::new(
|
||||
NonNegativeLengthOrPercentage::zero(),
|
||||
NonNegativeLengthOrPercentage::zero(),
|
||||
NonNegativeLengthPercentage::zero(),
|
||||
NonNegativeLengthPercentage::zero(),
|
||||
))
|
||||
}
|
||||
}
|
||||
|
@ -90,8 +90,8 @@ impl BorderRadius {
|
|||
/// Returns whether all the values are `0px`.
|
||||
pub fn all_zero(&self) -> bool {
|
||||
fn all(corner: &BorderCornerRadius) -> bool {
|
||||
fn is_zero(l: &NonNegativeLengthOrPercentage) -> bool {
|
||||
*l == NonNegativeLengthOrPercentage::zero()
|
||||
fn is_zero(l: &NonNegativeLengthPercentage) -> bool {
|
||||
*l == NonNegativeLengthPercentage::zero()
|
||||
}
|
||||
is_zero(corner.0.width()) && is_zero(corner.0.height())
|
||||
}
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
|
||||
//! Computed types for box properties.
|
||||
|
||||
use crate::values::computed::length::{LengthOrPercentage, NonNegativeLength};
|
||||
use crate::values::computed::length::{LengthPercentage, NonNegativeLength};
|
||||
use crate::values::computed::{Context, Number, ToComputedValue};
|
||||
use crate::values::generics::box_::AnimationIterationCount as GenericAnimationIterationCount;
|
||||
use crate::values::generics::box_::Perspective as GenericPerspective;
|
||||
|
@ -18,7 +18,7 @@ pub use crate::values::specified::box_::{OverscrollBehavior, ScrollSnapType};
|
|||
pub use crate::values::specified::box_::{TouchAction, TransitionProperty, WillChange};
|
||||
|
||||
/// A computed value for the `vertical-align` property.
|
||||
pub type VerticalAlign = GenericVerticalAlign<LengthOrPercentage>;
|
||||
pub type VerticalAlign = GenericVerticalAlign<LengthPercentage>;
|
||||
|
||||
/// A computed value for the `animation-iteration-count` property.
|
||||
pub type AnimationIterationCount = GenericAnimationIterationCount<Number>;
|
||||
|
|
|
@ -8,7 +8,7 @@ use crate::values::generics::flex::FlexBasis as GenericFlexBasis;
|
|||
|
||||
/// The `width` value type.
|
||||
#[cfg(feature = "servo")]
|
||||
pub type Width = crate::values::computed::NonNegativeLengthOrPercentageOrAuto;
|
||||
pub type Width = crate::values::computed::NonNegativeLengthPercentageOrAuto;
|
||||
|
||||
/// The `width` value type.
|
||||
#[cfg(feature = "gecko")]
|
||||
|
|
|
@ -4,8 +4,8 @@
|
|||
|
||||
//! Computed types for legacy Gecko-only properties.
|
||||
|
||||
use crate::values::computed::length::LengthOrPercentage;
|
||||
use crate::values::computed::length::LengthPercentage;
|
||||
use crate::values::generics::gecko::ScrollSnapPoint as GenericScrollSnapPoint;
|
||||
|
||||
/// A computed type for scroll snap points.
|
||||
pub type ScrollSnapPoint = GenericScrollSnapPoint<LengthOrPercentage>;
|
||||
pub type ScrollSnapPoint = GenericScrollSnapPoint<LengthPercentage>;
|
||||
|
|
|
@ -9,10 +9,8 @@
|
|||
|
||||
use crate::values::computed::position::Position;
|
||||
use crate::values::computed::url::ComputedImageUrl;
|
||||
#[cfg(feature = "gecko")]
|
||||
use crate::values::computed::Percentage;
|
||||
use crate::values::computed::{Angle, Color, Context};
|
||||
use crate::values::computed::{Length, LengthOrPercentage, NumberOrPercentage, ToComputedValue};
|
||||
use crate::values::computed::{Length, LengthPercentage, NumberOrPercentage, ToComputedValue};
|
||||
use crate::values::generics::image::{self as generic, CompatMode};
|
||||
use crate::values::specified::image::LineDirection as SpecifiedLineDirection;
|
||||
use crate::values::specified::position::{X, Y};
|
||||
|
@ -31,11 +29,11 @@ pub type Image = generic::Image<Gradient, MozImageRect, ComputedImageUrl>;
|
|||
/// Computed values for a CSS gradient.
|
||||
/// <https://drafts.csswg.org/css-images/#gradients>
|
||||
pub type Gradient =
|
||||
generic::Gradient<LineDirection, Length, LengthOrPercentage, Position, Color, Angle>;
|
||||
generic::Gradient<LineDirection, Length, LengthPercentage, Position, Color, Angle>;
|
||||
|
||||
/// A computed gradient kind.
|
||||
pub type GradientKind =
|
||||
generic::GradientKind<LineDirection, Length, LengthOrPercentage, Position, Angle>;
|
||||
generic::GradientKind<LineDirection, Length, LengthPercentage, Position, Angle>;
|
||||
|
||||
/// A computed gradient line direction.
|
||||
#[derive(Clone, Copy, Debug, MallocSizeOf, PartialEq)]
|
||||
|
@ -54,13 +52,13 @@ pub enum LineDirection {
|
|||
}
|
||||
|
||||
/// A computed radial gradient ending shape.
|
||||
pub type EndingShape = generic::EndingShape<Length, LengthOrPercentage>;
|
||||
pub type EndingShape = generic::EndingShape<Length, LengthPercentage>;
|
||||
|
||||
/// A computed gradient item.
|
||||
pub type GradientItem = generic::GradientItem<Color, LengthOrPercentage>;
|
||||
pub type GradientItem = generic::GradientItem<Color, LengthPercentage>;
|
||||
|
||||
/// A computed color stop.
|
||||
pub type ColorStop = generic::ColorStop<Color, LengthOrPercentage>;
|
||||
pub type ColorStop = generic::ColorStop<Color, LengthPercentage>;
|
||||
|
||||
/// Computed values for `-moz-image-rect(...)`.
|
||||
pub type MozImageRect = generic::MozImageRect<NumberOrPercentage, ComputedImageUrl>;
|
||||
|
@ -73,15 +71,10 @@ impl generic::LineDirection for LineDirection {
|
|||
LineDirection::Vertical(Y::Top) if compat_mode != CompatMode::Modern => true,
|
||||
LineDirection::Corner(..) => false,
|
||||
#[cfg(feature = "gecko")]
|
||||
LineDirection::MozPosition(
|
||||
Some(Position {
|
||||
horizontal: LengthOrPercentage::Percentage(Percentage(x)),
|
||||
vertical: LengthOrPercentage::Percentage(Percentage(y)),
|
||||
}),
|
||||
None,
|
||||
) => {
|
||||
LineDirection::MozPosition(Some(Position { ref vertical, ref horizontal }), None) => {
|
||||
// `50% 0%` is the default value for line direction.
|
||||
x == 0.5 && y == 0.0
|
||||
horizontal.as_percentage().map_or(false, |p| p.0 == 0.5) &&
|
||||
vertical.as_percentage().map_or(false, |p| p.0 == 0.0)
|
||||
},
|
||||
_ => false,
|
||||
}
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
//! `<length>` computed values, and related ones.
|
||||
|
||||
use super::{Context, Number, Percentage, ToComputedValue};
|
||||
use crate::values::animated::{Animate, Procedure, ToAnimatedValue, ToAnimatedZero};
|
||||
use crate::values::animated::{ToAnimatedValue};
|
||||
use crate::values::distance::{ComputeSquaredDistance, SquaredDistance};
|
||||
use crate::values::generics::length::MaxLength as GenericMaxLength;
|
||||
use crate::values::generics::length::MozLength as GenericMozLength;
|
||||
|
@ -67,16 +67,43 @@ impl ToComputedValue for specified::Length {
|
|||
}
|
||||
}
|
||||
|
||||
/// A `<length-percentage>` value. This can be either a `<length>`, a
|
||||
/// `<percentage>`, or a combination of both via `calc()`.
|
||||
///
|
||||
/// https://drafts.csswg.org/css-values-4/#typedef-length-percentage
|
||||
#[allow(missing_docs)]
|
||||
#[derive(Clone, Copy, Debug, MallocSizeOf, PartialEq, ToAnimatedZero)]
|
||||
pub struct CalcLengthOrPercentage {
|
||||
#[derive(Clone, Copy, Debug, MallocSizeOf, ToAnimatedZero)]
|
||||
pub struct LengthPercentage {
|
||||
#[animation(constant)]
|
||||
pub clamping_mode: AllowedNumericType,
|
||||
length: Length,
|
||||
pub percentage: Option<Percentage>,
|
||||
/// Whether this was from a calc() expression. This is needed because right
|
||||
/// now we don't treat calc() the same way as non-calc everywhere, but
|
||||
/// that's a bug in most cases.
|
||||
///
|
||||
/// Please don't add new uses of this that aren't for converting to Gecko's
|
||||
/// representation, or to interpolate values.
|
||||
///
|
||||
/// See https://github.com/w3c/csswg-drafts/issues/3482.
|
||||
#[animation(constant)]
|
||||
pub was_calc: bool,
|
||||
}
|
||||
|
||||
impl ComputeSquaredDistance for CalcLengthOrPercentage {
|
||||
// FIXME(emilio): This is a bit of a hack that can disappear as soon as we share
|
||||
// representation of LengthPercentage with Gecko. The issue here is that Gecko
|
||||
// uses CalcValue to represent position components, so they always come back as
|
||||
// was_calc == true, and we mess up in the transitions code.
|
||||
//
|
||||
// This was a pre-existing bug, though arguably so only in pretty obscure cases
|
||||
// like calc(0px + 5%) and such.
|
||||
impl PartialEq for LengthPercentage {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
self.length == other.length && self.percentage == other.percentage
|
||||
}
|
||||
}
|
||||
|
||||
impl ComputeSquaredDistance for LengthPercentage {
|
||||
#[inline]
|
||||
fn compute_squared_distance(&self, other: &Self) -> Result<SquaredDistance, ()> {
|
||||
// FIXME(nox): This looks incorrect to me, to add a distance between lengths
|
||||
|
@ -89,24 +116,36 @@ impl ComputeSquaredDistance for CalcLengthOrPercentage {
|
|||
}
|
||||
}
|
||||
|
||||
impl CalcLengthOrPercentage {
|
||||
/// Returns a new `CalcLengthOrPercentage`.
|
||||
impl LengthPercentage {
|
||||
/// Returns a new `LengthPercentage`.
|
||||
#[inline]
|
||||
pub fn new(length: Length, percentage: Option<Percentage>) -> Self {
|
||||
Self::with_clamping_mode(length, percentage, AllowedNumericType::All)
|
||||
Self::with_clamping_mode(
|
||||
length,
|
||||
percentage,
|
||||
AllowedNumericType::All,
|
||||
/* was_calc = */ false,
|
||||
)
|
||||
}
|
||||
|
||||
/// Returns a new `CalcLengthOrPercentage` with a specific clamping mode.
|
||||
/// Returns a new `LengthPercentage` with zero length and some percentage.
|
||||
pub fn new_percent(percentage: Percentage) -> Self {
|
||||
Self::new(Length::zero(), Some(percentage))
|
||||
}
|
||||
|
||||
/// Returns a new `LengthPercentage` with a specific clamping mode.
|
||||
#[inline]
|
||||
pub fn with_clamping_mode(
|
||||
length: Length,
|
||||
percentage: Option<Percentage>,
|
||||
clamping_mode: AllowedNumericType,
|
||||
was_calc: bool,
|
||||
) -> Self {
|
||||
Self {
|
||||
clamping_mode,
|
||||
length,
|
||||
percentage,
|
||||
was_calc,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -131,22 +170,38 @@ impl CalcLengthOrPercentage {
|
|||
self.length
|
||||
}
|
||||
|
||||
|
||||
/// Return the percentage value as CSSFloat.
|
||||
#[inline]
|
||||
pub fn percentage(&self) -> CSSFloat {
|
||||
self.percentage.map_or(0., |p| p.0)
|
||||
}
|
||||
|
||||
/// Returns the percentage component if this could be represented as a
|
||||
/// non-calc percentage.
|
||||
pub fn as_percentage(&self) -> Option<Percentage> {
|
||||
if self.length.px() != 0. {
|
||||
return None;
|
||||
}
|
||||
|
||||
let p = self.percentage?;
|
||||
if self.clamping_mode.clamp(p.0) != p.0 {
|
||||
return None;
|
||||
}
|
||||
|
||||
Some(p)
|
||||
}
|
||||
|
||||
/// Convert the computed value into used value.
|
||||
#[inline]
|
||||
pub fn to_used_value(&self, container_len: Option<Au>) -> Option<Au> {
|
||||
self.to_pixel_length(container_len).map(Au::from)
|
||||
pub fn maybe_to_used_value(&self, container_len: Option<Au>) -> Option<Au> {
|
||||
self.maybe_to_pixel_length(container_len).map(Au::from)
|
||||
}
|
||||
|
||||
/// If there are special rules for computing percentages in a value (e.g.
|
||||
/// the height property), they apply whenever a calc() expression contains
|
||||
/// percentages.
|
||||
pub fn to_pixel_length(&self, container_len: Option<Au>) -> Option<Length> {
|
||||
pub fn maybe_to_pixel_length(&self, container_len: Option<Au>) -> Option<Length> {
|
||||
match (container_len, self.percentage) {
|
||||
(Some(len), Some(percent)) => {
|
||||
let pixel = self.length.px() + len.scale_by(percent.0).to_f32_px();
|
||||
|
@ -158,92 +213,23 @@ impl CalcLengthOrPercentage {
|
|||
}
|
||||
}
|
||||
|
||||
impl From<LengthOrPercentage> for CalcLengthOrPercentage {
|
||||
fn from(len: LengthOrPercentage) -> CalcLengthOrPercentage {
|
||||
match len {
|
||||
LengthOrPercentage::Percentage(this) => {
|
||||
CalcLengthOrPercentage::new(Length::new(0.), Some(this))
|
||||
},
|
||||
LengthOrPercentage::Length(this) => CalcLengthOrPercentage::new(this, None),
|
||||
LengthOrPercentage::Calc(this) => this,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<LengthOrPercentageOrAuto> for Option<CalcLengthOrPercentage> {
|
||||
fn from(len: LengthOrPercentageOrAuto) -> Option<CalcLengthOrPercentage> {
|
||||
match len {
|
||||
LengthOrPercentageOrAuto::Percentage(this) => {
|
||||
Some(CalcLengthOrPercentage::new(Length::new(0.), Some(this)))
|
||||
},
|
||||
LengthOrPercentageOrAuto::Length(this) => Some(CalcLengthOrPercentage::new(this, None)),
|
||||
LengthOrPercentageOrAuto::Calc(this) => Some(this),
|
||||
LengthOrPercentageOrAuto::Auto => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<LengthOrPercentageOrNone> for Option<CalcLengthOrPercentage> {
|
||||
fn from(len: LengthOrPercentageOrNone) -> Option<CalcLengthOrPercentage> {
|
||||
match len {
|
||||
LengthOrPercentageOrNone::Percentage(this) => {
|
||||
Some(CalcLengthOrPercentage::new(Length::new(0.), Some(this)))
|
||||
},
|
||||
LengthOrPercentageOrNone::Length(this) => Some(CalcLengthOrPercentage::new(this, None)),
|
||||
LengthOrPercentageOrNone::Calc(this) => Some(this),
|
||||
LengthOrPercentageOrNone::None => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ToCss for CalcLengthOrPercentage {
|
||||
impl ToCss for LengthPercentage {
|
||||
fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
|
||||
where
|
||||
W: Write,
|
||||
{
|
||||
use num_traits::Zero;
|
||||
|
||||
let length = self.unclamped_length();
|
||||
match self.percentage {
|
||||
Some(p) => {
|
||||
if length.px() == 0. && self.clamping_mode.clamp(p.0) == p.0 {
|
||||
return p.to_css(dest);
|
||||
}
|
||||
},
|
||||
None => {
|
||||
if self.clamping_mode.clamp(length.px()) == length.px() {
|
||||
return length.to_css(dest);
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
dest.write_str("calc(")?;
|
||||
if let Some(percentage) = self.percentage {
|
||||
percentage.to_css(dest)?;
|
||||
if length.px() != 0. {
|
||||
dest.write_str(if length.px() < Zero::zero() {
|
||||
" - "
|
||||
} else {
|
||||
" + "
|
||||
})?;
|
||||
length.abs().to_css(dest)?;
|
||||
}
|
||||
} else {
|
||||
length.to_css(dest)?;
|
||||
}
|
||||
|
||||
dest.write_str(")")
|
||||
specified::LengthPercentage::from_computed_value(self).to_css(dest)
|
||||
}
|
||||
}
|
||||
|
||||
impl specified::CalcLengthOrPercentage {
|
||||
impl specified::CalcLengthPercentage {
|
||||
/// Compute the value, zooming any absolute units by the zoom function.
|
||||
fn to_computed_value_with_zoom<F>(
|
||||
&self,
|
||||
context: &Context,
|
||||
zoom_fn: F,
|
||||
base_size: FontBaseSize,
|
||||
) -> CalcLengthOrPercentage
|
||||
) -> LengthPercentage
|
||||
where
|
||||
F: Fn(Length) -> Length,
|
||||
{
|
||||
|
@ -277,10 +263,11 @@ impl specified::CalcLengthOrPercentage {
|
|||
}
|
||||
}
|
||||
|
||||
CalcLengthOrPercentage {
|
||||
LengthPercentage {
|
||||
clamping_mode: self.clamping_mode,
|
||||
length: Length::new(length.min(f32::MAX).max(f32::MIN)),
|
||||
percentage: self.percentage,
|
||||
was_calc: true,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -289,7 +276,7 @@ impl specified::CalcLengthOrPercentage {
|
|||
&self,
|
||||
context: &Context,
|
||||
base_size: FontBaseSize,
|
||||
) -> CalcLengthOrPercentage {
|
||||
) -> LengthPercentage {
|
||||
self.to_computed_value_with_zoom(
|
||||
context,
|
||||
|abs| context.maybe_zoom_text(abs.into()).0,
|
||||
|
@ -323,17 +310,17 @@ impl specified::CalcLengthOrPercentage {
|
|||
}
|
||||
}
|
||||
|
||||
impl ToComputedValue for specified::CalcLengthOrPercentage {
|
||||
type ComputedValue = CalcLengthOrPercentage;
|
||||
impl ToComputedValue for specified::CalcLengthPercentage {
|
||||
type ComputedValue = LengthPercentage;
|
||||
|
||||
fn to_computed_value(&self, context: &Context) -> CalcLengthOrPercentage {
|
||||
fn to_computed_value(&self, context: &Context) -> LengthPercentage {
|
||||
// normal properties don't zoom, and compute em units against the current style's font-size
|
||||
self.to_computed_value_with_zoom(context, |abs| abs, FontBaseSize::CurrentStyle)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn from_computed_value(computed: &CalcLengthOrPercentage) -> Self {
|
||||
specified::CalcLengthOrPercentage {
|
||||
fn from_computed_value(computed: &LengthPercentage) -> Self {
|
||||
specified::CalcLengthPercentage {
|
||||
clamping_mode: computed.clamping_mode,
|
||||
absolute: Some(AbsoluteLength::from_computed_value(&computed.length)),
|
||||
percentage: computed.percentage,
|
||||
|
@ -342,95 +329,33 @@ impl ToComputedValue for specified::CalcLengthOrPercentage {
|
|||
}
|
||||
}
|
||||
|
||||
#[allow(missing_docs)]
|
||||
#[animate(fallback = "Self::animate_fallback")]
|
||||
#[css(derive_debug)]
|
||||
#[derive(
|
||||
Animate,
|
||||
Clone,
|
||||
ComputeSquaredDistance,
|
||||
Copy,
|
||||
MallocSizeOf,
|
||||
PartialEq,
|
||||
ToAnimatedValue,
|
||||
ToAnimatedZero,
|
||||
ToCss,
|
||||
)]
|
||||
#[distance(fallback = "Self::compute_squared_distance_fallback")]
|
||||
pub enum LengthOrPercentage {
|
||||
Length(Length),
|
||||
Percentage(Percentage),
|
||||
Calc(CalcLengthOrPercentage),
|
||||
}
|
||||
|
||||
impl LengthOrPercentage {
|
||||
/// <https://drafts.csswg.org/css-transitions/#animtype-lpcalc>
|
||||
fn animate_fallback(&self, other: &Self, procedure: Procedure) -> Result<Self, ()> {
|
||||
// Special handling for zero values since these should not require calc().
|
||||
if self.is_definitely_zero() {
|
||||
return other.to_animated_zero()?.animate(other, procedure);
|
||||
}
|
||||
if other.is_definitely_zero() {
|
||||
return self.animate(&self.to_animated_zero()?, procedure);
|
||||
}
|
||||
|
||||
let this = CalcLengthOrPercentage::from(*self);
|
||||
let other = CalcLengthOrPercentage::from(*other);
|
||||
Ok(LengthOrPercentage::Calc(this.animate(&other, procedure)?))
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn compute_squared_distance_fallback(&self, other: &Self) -> Result<SquaredDistance, ()> {
|
||||
CalcLengthOrPercentage::compute_squared_distance(&(*self).into(), &(*other).into())
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Au> for LengthOrPercentage {
|
||||
#[inline]
|
||||
fn from(length: Au) -> Self {
|
||||
LengthOrPercentage::Length(length.into())
|
||||
}
|
||||
}
|
||||
|
||||
impl LengthOrPercentage {
|
||||
impl LengthPercentage {
|
||||
#[inline]
|
||||
#[allow(missing_docs)]
|
||||
pub fn zero() -> LengthOrPercentage {
|
||||
LengthOrPercentage::Length(Length::new(0.))
|
||||
pub fn zero() -> LengthPercentage {
|
||||
LengthPercentage::new(Length::new(0.), None)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
/// 1px length value for SVG defaults
|
||||
pub fn one() -> LengthOrPercentage {
|
||||
LengthOrPercentage::Length(Length::new(1.))
|
||||
#[inline]
|
||||
pub fn one() -> LengthPercentage {
|
||||
LengthPercentage::new(Length::new(1.), None)
|
||||
}
|
||||
|
||||
/// Returns true if the computed value is absolute 0 or 0%.
|
||||
///
|
||||
/// (Returns false for calc() values, even if ones that may resolve to zero.)
|
||||
#[inline]
|
||||
pub fn is_definitely_zero(&self) -> bool {
|
||||
use self::LengthOrPercentage::*;
|
||||
match *self {
|
||||
Length(l) => l.px() == 0.0,
|
||||
Percentage(p) => p.0 == 0.0,
|
||||
Calc(_) => false,
|
||||
}
|
||||
self.unclamped_length().px() == 0.0 && self.percentage.map_or(true, |p| p.0 == 0.0)
|
||||
}
|
||||
|
||||
// CSSFloat doesn't implement Hash, so does CSSPixelLength. Therefore, we still use Au as the
|
||||
// hash key.
|
||||
#[allow(missing_docs)]
|
||||
pub fn to_hash_key(&self) -> (Au, NotNan<f32>) {
|
||||
use self::LengthOrPercentage::*;
|
||||
match *self {
|
||||
Length(l) => (Au::from(l), NotNan::new(0.0).unwrap()),
|
||||
Percentage(p) => (Au(0), NotNan::new(p.0).unwrap()),
|
||||
Calc(c) => (
|
||||
Au::from(c.unclamped_length()),
|
||||
NotNan::new(c.percentage()).unwrap(),
|
||||
),
|
||||
}
|
||||
(
|
||||
Au::from(self.unclamped_length()),
|
||||
NotNan::new(self.percentage()).unwrap(),
|
||||
)
|
||||
}
|
||||
|
||||
/// Returns the used value.
|
||||
|
@ -440,124 +365,123 @@ impl LengthOrPercentage {
|
|||
|
||||
/// Returns the used value as CSSPixelLength.
|
||||
pub fn to_pixel_length(&self, containing_length: Au) -> Length {
|
||||
match *self {
|
||||
LengthOrPercentage::Length(length) => length,
|
||||
LengthOrPercentage::Percentage(p) => containing_length.scale_by(p.0).into(),
|
||||
LengthOrPercentage::Calc(ref calc) => {
|
||||
calc.to_pixel_length(Some(containing_length)).unwrap()
|
||||
},
|
||||
}
|
||||
self.maybe_to_pixel_length(Some(containing_length)).unwrap()
|
||||
}
|
||||
|
||||
/// Returns the clamped non-negative values.
|
||||
///
|
||||
/// TODO(emilio): It's a bit unfortunate that this depends on whether the
|
||||
/// value was a `calc()` value or not. Should it?
|
||||
#[inline]
|
||||
pub fn clamp_to_non_negative(self) -> Self {
|
||||
match self {
|
||||
LengthOrPercentage::Length(length) => {
|
||||
LengthOrPercentage::Length(length.clamp_to_non_negative())
|
||||
},
|
||||
LengthOrPercentage::Percentage(percentage) => {
|
||||
LengthOrPercentage::Percentage(percentage.clamp_to_non_negative())
|
||||
},
|
||||
_ => self,
|
||||
if self.was_calc {
|
||||
return Self::with_clamping_mode(
|
||||
self.length,
|
||||
self.percentage,
|
||||
AllowedNumericType::NonNegative,
|
||||
self.was_calc,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ToComputedValue for specified::LengthOrPercentage {
|
||||
type ComputedValue = LengthOrPercentage;
|
||||
|
||||
fn to_computed_value(&self, context: &Context) -> LengthOrPercentage {
|
||||
match *self {
|
||||
specified::LengthOrPercentage::Length(ref value) => {
|
||||
LengthOrPercentage::Length(value.to_computed_value(context))
|
||||
},
|
||||
specified::LengthOrPercentage::Percentage(value) => {
|
||||
LengthOrPercentage::Percentage(value)
|
||||
},
|
||||
specified::LengthOrPercentage::Calc(ref calc) => {
|
||||
LengthOrPercentage::Calc((**calc).to_computed_value(context))
|
||||
},
|
||||
debug_assert!(self.percentage.is_none() || self.unclamped_length() == Length::zero());
|
||||
if let Some(p) = self.percentage {
|
||||
return Self::with_clamping_mode(
|
||||
Length::zero(),
|
||||
Some(p.clamp_to_non_negative()),
|
||||
AllowedNumericType::NonNegative,
|
||||
self.was_calc,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
fn from_computed_value(computed: &LengthOrPercentage) -> Self {
|
||||
match *computed {
|
||||
LengthOrPercentage::Length(value) => {
|
||||
specified::LengthOrPercentage::Length(ToComputedValue::from_computed_value(&value))
|
||||
},
|
||||
LengthOrPercentage::Percentage(value) => {
|
||||
specified::LengthOrPercentage::Percentage(value)
|
||||
},
|
||||
LengthOrPercentage::Calc(ref calc) => specified::LengthOrPercentage::Calc(Box::new(
|
||||
ToComputedValue::from_computed_value(calc),
|
||||
)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl IsZeroLength for LengthOrPercentage {
|
||||
#[inline]
|
||||
fn is_zero_length(&self) -> bool {
|
||||
match *self {
|
||||
LengthOrPercentage::Length(l) => l.0 == 0.0,
|
||||
LengthOrPercentage::Percentage(p) => p.0 == 0.0,
|
||||
LengthOrPercentage::Calc(c) => c.unclamped_length().0 == 0.0 && c.percentage() == 0.0,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(missing_docs)]
|
||||
#[animate(fallback = "Self::animate_fallback")]
|
||||
#[css(derive_debug)]
|
||||
#[derive(Animate, Clone, ComputeSquaredDistance, Copy, MallocSizeOf, PartialEq, ToCss)]
|
||||
#[distance(fallback = "Self::compute_squared_distance_fallback")]
|
||||
pub enum LengthOrPercentageOrAuto {
|
||||
Length(Length),
|
||||
Percentage(Percentage),
|
||||
Auto,
|
||||
Calc(CalcLengthOrPercentage),
|
||||
}
|
||||
|
||||
impl LengthOrPercentageOrAuto {
|
||||
/// <https://drafts.csswg.org/css-transitions/#animtype-lpcalc>
|
||||
fn animate_fallback(&self, other: &Self, procedure: Procedure) -> Result<Self, ()> {
|
||||
let this = <Option<CalcLengthOrPercentage>>::from(*self);
|
||||
let other = <Option<CalcLengthOrPercentage>>::from(*other);
|
||||
Ok(LengthOrPercentageOrAuto::Calc(
|
||||
this.animate(&other, procedure)?.ok_or(())?,
|
||||
))
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn compute_squared_distance_fallback(&self, other: &Self) -> Result<SquaredDistance, ()> {
|
||||
<Option<CalcLengthOrPercentage>>::compute_squared_distance(
|
||||
&(*self).into(),
|
||||
&(*other).into(),
|
||||
Self::with_clamping_mode(
|
||||
self.length.clamp_to_non_negative(),
|
||||
None,
|
||||
AllowedNumericType::NonNegative,
|
||||
self.was_calc,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/// A wrapper of LengthOrPercentageOrAuto, whose value must be >= 0.
|
||||
pub type NonNegativeLengthOrPercentageOrAuto = NonNegative<LengthOrPercentageOrAuto>;
|
||||
impl ToComputedValue for specified::LengthPercentage {
|
||||
type ComputedValue = LengthPercentage;
|
||||
|
||||
impl IsAuto for NonNegativeLengthOrPercentageOrAuto {
|
||||
fn to_computed_value(&self, context: &Context) -> LengthPercentage {
|
||||
match *self {
|
||||
specified::LengthPercentage::Length(ref value) => {
|
||||
LengthPercentage::new(value.to_computed_value(context), None)
|
||||
},
|
||||
specified::LengthPercentage::Percentage(value) => {
|
||||
LengthPercentage::new_percent(value)
|
||||
},
|
||||
specified::LengthPercentage::Calc(ref calc) => {
|
||||
(**calc).to_computed_value(context)
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
fn from_computed_value(computed: &LengthPercentage) -> Self {
|
||||
let length = computed.unclamped_length();
|
||||
if let Some(p) = computed.as_percentage() {
|
||||
return specified::LengthPercentage::Percentage(p)
|
||||
}
|
||||
|
||||
let percentage = computed.percentage;
|
||||
if percentage.is_none() &&
|
||||
computed.clamping_mode.clamp(length.px()) == length.px() {
|
||||
return specified::LengthPercentage::Length(
|
||||
ToComputedValue::from_computed_value(&length)
|
||||
)
|
||||
}
|
||||
|
||||
specified::LengthPercentage::Calc(Box::new(
|
||||
ToComputedValue::from_computed_value(computed),
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
impl IsZeroLength for LengthPercentage {
|
||||
#[inline]
|
||||
fn is_zero_length(&self) -> bool {
|
||||
self.is_definitely_zero()
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(missing_docs)]
|
||||
#[css(derive_debug)]
|
||||
#[derive(Animate, Clone, ComputeSquaredDistance, Copy, MallocSizeOf, PartialEq, ToAnimatedZero, ToCss)]
|
||||
pub enum LengthPercentageOrAuto {
|
||||
LengthPercentage(LengthPercentage),
|
||||
Auto,
|
||||
}
|
||||
|
||||
impl LengthPercentageOrAuto {
|
||||
/// Returns the `0` value.
|
||||
#[inline]
|
||||
pub fn zero() -> Self {
|
||||
LengthPercentageOrAuto::LengthPercentage(LengthPercentage::zero())
|
||||
}
|
||||
}
|
||||
|
||||
/// A wrapper of LengthPercentageOrAuto, whose value must be >= 0.
|
||||
pub type NonNegativeLengthPercentageOrAuto = NonNegative<LengthPercentageOrAuto>;
|
||||
|
||||
impl IsAuto for NonNegativeLengthPercentageOrAuto {
|
||||
#[inline]
|
||||
fn is_auto(&self) -> bool {
|
||||
*self == Self::auto()
|
||||
}
|
||||
}
|
||||
|
||||
impl NonNegativeLengthOrPercentageOrAuto {
|
||||
impl NonNegativeLengthPercentageOrAuto {
|
||||
/// `auto`
|
||||
#[inline]
|
||||
pub fn auto() -> Self {
|
||||
NonNegative(LengthOrPercentageOrAuto::Auto)
|
||||
NonNegative(LengthPercentageOrAuto::Auto)
|
||||
}
|
||||
}
|
||||
|
||||
impl ToAnimatedValue for NonNegativeLengthOrPercentageOrAuto {
|
||||
type AnimatedValue = LengthOrPercentageOrAuto;
|
||||
impl ToAnimatedValue for NonNegativeLengthPercentageOrAuto {
|
||||
type AnimatedValue = LengthPercentageOrAuto;
|
||||
|
||||
#[inline]
|
||||
fn to_animated_value(self) -> Self::AnimatedValue {
|
||||
|
@ -570,190 +494,154 @@ impl ToAnimatedValue for NonNegativeLengthOrPercentageOrAuto {
|
|||
}
|
||||
}
|
||||
|
||||
impl LengthOrPercentageOrAuto {
|
||||
impl LengthPercentageOrAuto {
|
||||
/// Returns true if the computed value is absolute 0 or 0%.
|
||||
///
|
||||
/// (Returns false for calc() values, even if ones that may resolve to zero.)
|
||||
#[inline]
|
||||
pub fn is_definitely_zero(&self) -> bool {
|
||||
use self::LengthOrPercentageOrAuto::*;
|
||||
use self::LengthPercentageOrAuto::*;
|
||||
match *self {
|
||||
Length(l) => l.px() == 0.0,
|
||||
Percentage(p) => p.0 == 0.0,
|
||||
Calc(_) | Auto => false,
|
||||
LengthPercentage(ref l) => l.is_definitely_zero(),
|
||||
Auto => false,
|
||||
}
|
||||
}
|
||||
|
||||
fn clamp_to_non_negative(self) -> Self {
|
||||
use self::LengthOrPercentageOrAuto::*;
|
||||
/// Clamps the value to a non-negative value.
|
||||
pub fn clamp_to_non_negative(self) -> Self {
|
||||
use self::LengthPercentageOrAuto::*;
|
||||
match self {
|
||||
Length(l) => Length(l.clamp_to_non_negative()),
|
||||
Percentage(p) => Percentage(p.clamp_to_non_negative()),
|
||||
_ => self,
|
||||
LengthPercentage(l) => LengthPercentage(l.clamp_to_non_negative()),
|
||||
Auto => Auto,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ToComputedValue for specified::LengthOrPercentageOrAuto {
|
||||
type ComputedValue = LengthOrPercentageOrAuto;
|
||||
impl ToComputedValue for specified::LengthPercentageOrAuto {
|
||||
type ComputedValue = LengthPercentageOrAuto;
|
||||
|
||||
#[inline]
|
||||
fn to_computed_value(&self, context: &Context) -> LengthOrPercentageOrAuto {
|
||||
fn to_computed_value(&self, context: &Context) -> LengthPercentageOrAuto {
|
||||
match *self {
|
||||
specified::LengthOrPercentageOrAuto::Length(ref value) => {
|
||||
LengthOrPercentageOrAuto::Length(value.to_computed_value(context))
|
||||
},
|
||||
specified::LengthOrPercentageOrAuto::Percentage(value) => {
|
||||
LengthOrPercentageOrAuto::Percentage(value)
|
||||
},
|
||||
specified::LengthOrPercentageOrAuto::Auto => LengthOrPercentageOrAuto::Auto,
|
||||
specified::LengthOrPercentageOrAuto::Calc(ref calc) => {
|
||||
LengthOrPercentageOrAuto::Calc((**calc).to_computed_value(context))
|
||||
specified::LengthPercentageOrAuto::LengthPercentage(ref value) => {
|
||||
LengthPercentageOrAuto::LengthPercentage(
|
||||
value.to_computed_value(context),
|
||||
)
|
||||
},
|
||||
specified::LengthPercentageOrAuto::Auto => LengthPercentageOrAuto::Auto,
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn from_computed_value(computed: &LengthOrPercentageOrAuto) -> Self {
|
||||
fn from_computed_value(computed: &LengthPercentageOrAuto) -> Self {
|
||||
match *computed {
|
||||
LengthOrPercentageOrAuto::Auto => specified::LengthOrPercentageOrAuto::Auto,
|
||||
LengthOrPercentageOrAuto::Length(value) => specified::LengthOrPercentageOrAuto::Length(
|
||||
ToComputedValue::from_computed_value(&value),
|
||||
),
|
||||
LengthOrPercentageOrAuto::Percentage(value) => {
|
||||
specified::LengthOrPercentageOrAuto::Percentage(value)
|
||||
LengthPercentageOrAuto::Auto => specified::LengthPercentageOrAuto::Auto,
|
||||
LengthPercentageOrAuto::LengthPercentage(ref value) => {
|
||||
specified::LengthPercentageOrAuto::LengthPercentage(
|
||||
ToComputedValue::from_computed_value(value),
|
||||
)
|
||||
},
|
||||
LengthOrPercentageOrAuto::Calc(calc) => specified::LengthOrPercentageOrAuto::Calc(
|
||||
Box::new(ToComputedValue::from_computed_value(&calc)),
|
||||
),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(missing_docs)]
|
||||
#[animate(fallback = "Self::animate_fallback")]
|
||||
#[cfg_attr(feature = "servo", derive(MallocSizeOf))]
|
||||
#[css(derive_debug)]
|
||||
#[derive(Animate, Clone, ComputeSquaredDistance, Copy, PartialEq, ToCss)]
|
||||
#[distance(fallback = "Self::compute_squared_distance_fallback")]
|
||||
pub enum LengthOrPercentageOrNone {
|
||||
Length(Length),
|
||||
Percentage(Percentage),
|
||||
Calc(CalcLengthOrPercentage),
|
||||
#[derive(Animate, Clone, ComputeSquaredDistance, Copy, MallocSizeOf, PartialEq, ToAnimatedZero, ToCss)]
|
||||
pub enum LengthPercentageOrNone {
|
||||
LengthPercentage(LengthPercentage),
|
||||
None,
|
||||
}
|
||||
|
||||
impl LengthOrPercentageOrNone {
|
||||
/// <https://drafts.csswg.org/css-transitions/#animtype-lpcalc>
|
||||
fn animate_fallback(&self, other: &Self, procedure: Procedure) -> Result<Self, ()> {
|
||||
let this = <Option<CalcLengthOrPercentage>>::from(*self);
|
||||
let other = <Option<CalcLengthOrPercentage>>::from(*other);
|
||||
Ok(LengthOrPercentageOrNone::Calc(
|
||||
this.animate(&other, procedure)?.ok_or(())?,
|
||||
))
|
||||
}
|
||||
|
||||
fn compute_squared_distance_fallback(&self, other: &Self) -> Result<SquaredDistance, ()> {
|
||||
<Option<CalcLengthOrPercentage>>::compute_squared_distance(
|
||||
&(*self).into(),
|
||||
&(*other).into(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl LengthOrPercentageOrNone {
|
||||
impl LengthPercentageOrNone {
|
||||
/// Returns the used value.
|
||||
pub fn to_used_value(&self, containing_length: Au) -> Option<Au> {
|
||||
match *self {
|
||||
LengthOrPercentageOrNone::None => None,
|
||||
LengthOrPercentageOrNone::Length(length) => Some(Au::from(length)),
|
||||
LengthOrPercentageOrNone::Percentage(percent) => {
|
||||
Some(containing_length.scale_by(percent.0))
|
||||
LengthPercentageOrNone::None => None,
|
||||
LengthPercentageOrNone::LengthPercentage(ref lp) => {
|
||||
Some(lp.to_used_value(containing_length))
|
||||
},
|
||||
LengthOrPercentageOrNone::Calc(ref calc) => calc.to_used_value(Some(containing_length)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ToComputedValue for specified::LengthOrPercentageOrNone {
|
||||
type ComputedValue = LengthOrPercentageOrNone;
|
||||
// FIXME(emilio): Derive this.
|
||||
impl ToComputedValue for specified::LengthPercentageOrNone {
|
||||
type ComputedValue = LengthPercentageOrNone;
|
||||
|
||||
#[inline]
|
||||
fn to_computed_value(&self, context: &Context) -> LengthOrPercentageOrNone {
|
||||
fn to_computed_value(&self, context: &Context) -> LengthPercentageOrNone {
|
||||
match *self {
|
||||
specified::LengthOrPercentageOrNone::Length(ref value) => {
|
||||
LengthOrPercentageOrNone::Length(value.to_computed_value(context))
|
||||
specified::LengthPercentageOrNone::LengthPercentage(ref value) => {
|
||||
LengthPercentageOrNone::LengthPercentage(value.to_computed_value(context))
|
||||
},
|
||||
specified::LengthOrPercentageOrNone::Percentage(value) => {
|
||||
LengthOrPercentageOrNone::Percentage(value)
|
||||
},
|
||||
specified::LengthOrPercentageOrNone::Calc(ref calc) => {
|
||||
LengthOrPercentageOrNone::Calc((**calc).to_computed_value(context))
|
||||
},
|
||||
specified::LengthOrPercentageOrNone::None => LengthOrPercentageOrNone::None,
|
||||
specified::LengthPercentageOrNone::None => LengthPercentageOrNone::None,
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn from_computed_value(computed: &LengthOrPercentageOrNone) -> Self {
|
||||
fn from_computed_value(computed: &LengthPercentageOrNone) -> Self {
|
||||
match *computed {
|
||||
LengthOrPercentageOrNone::None => specified::LengthOrPercentageOrNone::None,
|
||||
LengthOrPercentageOrNone::Length(value) => specified::LengthOrPercentageOrNone::Length(
|
||||
ToComputedValue::from_computed_value(&value),
|
||||
),
|
||||
LengthOrPercentageOrNone::Percentage(value) => {
|
||||
specified::LengthOrPercentageOrNone::Percentage(value)
|
||||
LengthPercentageOrNone::None => specified::LengthPercentageOrNone::None,
|
||||
LengthPercentageOrNone::LengthPercentage(value) => {
|
||||
specified::LengthPercentageOrNone::LengthPercentage(
|
||||
ToComputedValue::from_computed_value(&value),
|
||||
)
|
||||
},
|
||||
LengthOrPercentageOrNone::Calc(calc) => specified::LengthOrPercentageOrNone::Calc(
|
||||
Box::new(ToComputedValue::from_computed_value(&calc)),
|
||||
),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A wrapper of LengthOrPercentage, whose value must be >= 0.
|
||||
pub type NonNegativeLengthOrPercentage = NonNegative<LengthOrPercentage>;
|
||||
/// A wrapper of LengthPercentage, whose value must be >= 0.
|
||||
pub type NonNegativeLengthPercentage = NonNegative<LengthPercentage>;
|
||||
|
||||
impl ToAnimatedValue for NonNegativeLengthOrPercentage {
|
||||
type AnimatedValue = LengthOrPercentage;
|
||||
impl ToAnimatedValue for NonNegativeLengthPercentage {
|
||||
type AnimatedValue = LengthPercentage;
|
||||
|
||||
#[inline]
|
||||
fn to_animated_value(self) -> Self::AnimatedValue {
|
||||
self.into()
|
||||
self.0
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn from_animated_value(animated: Self::AnimatedValue) -> Self {
|
||||
animated.clamp_to_non_negative().into()
|
||||
NonNegative(animated.clamp_to_non_negative())
|
||||
}
|
||||
}
|
||||
|
||||
impl From<NonNegativeLength> for NonNegativeLengthOrPercentage {
|
||||
impl From<NonNegativeLength> for NonNegativeLengthPercentage {
|
||||
#[inline]
|
||||
fn from(length: NonNegativeLength) -> Self {
|
||||
LengthOrPercentage::Length(length.0).into()
|
||||
LengthPercentage::new(length.0, None).into()
|
||||
}
|
||||
}
|
||||
|
||||
impl From<LengthOrPercentage> for NonNegativeLengthOrPercentage {
|
||||
impl From<LengthPercentage> for NonNegativeLengthPercentage {
|
||||
#[inline]
|
||||
fn from(lop: LengthOrPercentage) -> Self {
|
||||
NonNegative::<LengthOrPercentage>(lop)
|
||||
fn from(lp: LengthPercentage) -> Self {
|
||||
NonNegative::<LengthPercentage>(lp)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<NonNegativeLengthOrPercentage> for LengthOrPercentage {
|
||||
impl From<NonNegativeLengthPercentage> for LengthPercentage {
|
||||
#[inline]
|
||||
fn from(lop: NonNegativeLengthOrPercentage) -> LengthOrPercentage {
|
||||
lop.0
|
||||
fn from(lp: NonNegativeLengthPercentage) -> LengthPercentage {
|
||||
lp.0
|
||||
}
|
||||
}
|
||||
|
||||
impl NonNegativeLengthOrPercentage {
|
||||
// TODO(emilio): This is a really generic impl which is only needed to implement
|
||||
// Animated and co for Spacing<>. Get rid of this, probably?
|
||||
impl From<Au> for LengthPercentage {
|
||||
#[inline]
|
||||
fn from(length: Au) -> Self {
|
||||
LengthPercentage::new(length.into(), None)
|
||||
}
|
||||
}
|
||||
|
||||
impl NonNegativeLengthPercentage {
|
||||
/// Get zero value.
|
||||
#[inline]
|
||||
pub fn zero() -> Self {
|
||||
NonNegative::<LengthOrPercentage>(LengthOrPercentage::zero())
|
||||
NonNegative::<LengthPercentage>(LengthPercentage::zero())
|
||||
}
|
||||
|
||||
/// Returns true if the computed value is absolute 0 or 0%.
|
||||
|
@ -798,11 +686,6 @@ impl CSSPixelLength {
|
|||
self.0
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn clamp_to_non_negative(self) -> Self {
|
||||
Self::new(self.px().max(0.))
|
||||
}
|
||||
|
||||
/// Return the length with app_unit i32 type.
|
||||
#[inline]
|
||||
pub fn to_i32_au(&self) -> i32 {
|
||||
|
@ -810,11 +693,19 @@ impl CSSPixelLength {
|
|||
}
|
||||
|
||||
/// Return the absolute value of this length.
|
||||
#[inline]
|
||||
pub fn abs(self) -> Self {
|
||||
CSSPixelLength::new(self.0.abs())
|
||||
}
|
||||
|
||||
/// Return the clamped value of this length.
|
||||
#[inline]
|
||||
pub fn clamp_to_non_negative(self) -> Self {
|
||||
CSSPixelLength::new(self.0.max(0.))
|
||||
}
|
||||
|
||||
/// Zero value
|
||||
#[inline]
|
||||
pub fn zero() -> Self {
|
||||
CSSPixelLength::new(0.)
|
||||
}
|
||||
|
@ -963,8 +854,8 @@ pub type NonNegativeLengthOrAuto = Either<NonNegativeLength, Auto>;
|
|||
/// Either a computed NonNegativeLength or the `normal` keyword.
|
||||
pub type NonNegativeLengthOrNormal = Either<NonNegativeLength, Normal>;
|
||||
|
||||
/// Either a computed NonNegativeLengthOrPercentage or the `normal` keyword.
|
||||
pub type NonNegativeLengthOrPercentageOrNormal = Either<NonNegativeLengthOrPercentage, Normal>;
|
||||
/// Either a computed NonNegativeLengthPercentage or the `normal` keyword.
|
||||
pub type NonNegativeLengthPercentageOrNormal = Either<NonNegativeLengthPercentage, Normal>;
|
||||
|
||||
/// A type for possible values for min- and max- flavors of width, height,
|
||||
/// block-size, and inline-size.
|
||||
|
@ -994,23 +885,23 @@ pub enum ExtremumLength {
|
|||
}
|
||||
|
||||
/// A computed value for `min-width`, `min-height`, `width` or `height` property.
|
||||
pub type MozLength = GenericMozLength<LengthOrPercentageOrAuto>;
|
||||
pub type MozLength = GenericMozLength<LengthPercentageOrAuto>;
|
||||
|
||||
impl MozLength {
|
||||
/// Returns the `auto` value.
|
||||
#[inline]
|
||||
pub fn auto() -> Self {
|
||||
GenericMozLength::LengthOrPercentageOrAuto(LengthOrPercentageOrAuto::Auto)
|
||||
GenericMozLength::LengthPercentageOrAuto(LengthPercentageOrAuto::Auto)
|
||||
}
|
||||
}
|
||||
|
||||
/// A computed value for `max-width` or `min-height` property.
|
||||
pub type MaxLength = GenericMaxLength<LengthOrPercentageOrNone>;
|
||||
pub type MaxLength = GenericMaxLength<LengthPercentageOrNone>;
|
||||
|
||||
impl MaxLength {
|
||||
/// Returns the `none` value.
|
||||
#[inline]
|
||||
pub fn none() -> Self {
|
||||
GenericMaxLength::LengthOrPercentageOrNone(LengthOrPercentageOrNone::None)
|
||||
GenericMaxLength::LengthPercentageOrNone(LengthPercentageOrNone::None)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -62,9 +62,9 @@ pub use self::font::{MozScriptLevel, MozScriptMinSize, MozScriptSizeMultiplier,
|
|||
pub use self::gecko::ScrollSnapPoint;
|
||||
pub use self::image::{Gradient, GradientItem, Image, ImageLayer, LineDirection, MozImageRect};
|
||||
pub use self::length::{CSSPixelLength, ExtremumLength, NonNegativeLength};
|
||||
pub use self::length::{CalcLengthOrPercentage, Length, LengthOrNumber, LengthOrPercentage};
|
||||
pub use self::length::{LengthOrPercentageOrAuto, LengthOrPercentageOrNone, MaxLength, MozLength};
|
||||
pub use self::length::{NonNegativeLengthOrPercentage, NonNegativeLengthOrPercentageOrAuto};
|
||||
pub use self::length::{Length, LengthOrNumber, LengthPercentage};
|
||||
pub use self::length::{LengthPercentageOrAuto, LengthPercentageOrNone, MaxLength, MozLength};
|
||||
pub use self::length::{NonNegativeLengthPercentage, NonNegativeLengthPercentageOrAuto};
|
||||
#[cfg(feature = "gecko")]
|
||||
pub use self::list::ListStyleType;
|
||||
pub use self::list::{QuotePair, Quotes};
|
||||
|
@ -689,20 +689,20 @@ impl ToCss for ClipRect {
|
|||
pub type ClipRectOrAuto = Either<ClipRect, Auto>;
|
||||
|
||||
/// The computed value of a grid `<track-breadth>`
|
||||
pub type TrackBreadth = GenericTrackBreadth<LengthOrPercentage>;
|
||||
pub type TrackBreadth = GenericTrackBreadth<LengthPercentage>;
|
||||
|
||||
/// The computed value of a grid `<track-size>`
|
||||
pub type TrackSize = GenericTrackSize<LengthOrPercentage>;
|
||||
pub type TrackSize = GenericTrackSize<LengthPercentage>;
|
||||
|
||||
/// The computed value of a grid `<track-list>`
|
||||
/// (could also be `<auto-track-list>` or `<explicit-track-list>`)
|
||||
pub type TrackList = GenericTrackList<LengthOrPercentage, Integer>;
|
||||
pub type TrackList = GenericTrackList<LengthPercentage, Integer>;
|
||||
|
||||
/// The computed value of a `<grid-line>`.
|
||||
pub type GridLine = GenericGridLine<Integer>;
|
||||
|
||||
/// `<grid-template-rows> | <grid-template-columns>`
|
||||
pub type GridTemplateComponent = GenericGridTemplateComponent<LengthOrPercentage, Integer>;
|
||||
pub type GridTemplateComponent = GenericGridTemplateComponent<LengthPercentage, Integer>;
|
||||
|
||||
impl ClipRectOrAuto {
|
||||
/// Return an auto (default for clip-rect and image-region) value
|
||||
|
|
|
@ -7,7 +7,7 @@
|
|||
//!
|
||||
//! [position]: https://drafts.csswg.org/css-backgrounds-3/#position
|
||||
|
||||
use crate::values::computed::{Integer, LengthOrPercentage, Percentage};
|
||||
use crate::values::computed::{Integer, LengthPercentage, Percentage};
|
||||
use crate::values::generics::position::Position as GenericPosition;
|
||||
use crate::values::generics::position::ZIndex as GenericZIndex;
|
||||
pub use crate::values::specified::position::{GridAutoFlow, GridTemplateAreas};
|
||||
|
@ -18,25 +18,25 @@ use style_traits::{CssWriter, ToCss};
|
|||
pub type Position = GenericPosition<HorizontalPosition, VerticalPosition>;
|
||||
|
||||
/// The computed value of a CSS horizontal position.
|
||||
pub type HorizontalPosition = LengthOrPercentage;
|
||||
pub type HorizontalPosition = LengthPercentage;
|
||||
|
||||
/// The computed value of a CSS vertical position.
|
||||
pub type VerticalPosition = LengthOrPercentage;
|
||||
pub type VerticalPosition = LengthPercentage;
|
||||
|
||||
impl Position {
|
||||
/// `50% 50%`
|
||||
#[inline]
|
||||
pub fn center() -> Self {
|
||||
Self::new(
|
||||
LengthOrPercentage::Percentage(Percentage(0.5)),
|
||||
LengthOrPercentage::Percentage(Percentage(0.5)),
|
||||
LengthPercentage::new_percent(Percentage(0.5)),
|
||||
LengthPercentage::new_percent(Percentage(0.5)),
|
||||
)
|
||||
}
|
||||
|
||||
/// `0% 0%`
|
||||
#[inline]
|
||||
pub fn zero() -> Self {
|
||||
Self::new(LengthOrPercentage::zero(), LengthOrPercentage::zero())
|
||||
Self::new(LengthPercentage::zero(), LengthPercentage::zero())
|
||||
}
|
||||
}
|
||||
|
||||
|
|
Некоторые файлы не были показаны из-за слишком большого количества измененных файлов Показать больше
Загрузка…
Ссылка в новой задаче