Merge mozilla-central to inbound. a=merge CLOSED TREE

This commit is contained in:
Brindusan Cristian 2019-01-07 18:46:25 +02:00
Родитель 34a3926bd5 4d62b74678
Коммит 331818d5aa
170 изменённых файлов: 5790 добавлений и 2018 удалений

Просмотреть файл

@ -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 &lt;input&gt; element: <input type=\"text\" value=\"default value\"><br>\n" +
"and a &lt;textarea&gt; 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 &lt;input&gt; element: <input type=\"text\" value=\"default value\"><br>\n" +
"and a &lt;textarea&gt; 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 &lt;input&gt; element: <input type=\"text\" value=\"default value\"><br>\n" +
"and a &lt;textarea&gt; 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())
}
}

Некоторые файлы не были показаны из-за слишком большого количества измененных файлов Показать больше