зеркало из https://github.com/mozilla/gecko-dev.git
Merge m-c to inbound, a=merge
This commit is contained in:
Коммит
8dafd1ef34
|
@ -483,7 +483,15 @@ toolbar:not(#TabsToolbar) > #personal-bookmarks {
|
||||||
list-style-image: none;
|
list-style-image: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
#urlbar:not([actiontype="switchtab"]) > #urlbar-display-box {
|
#urlbar:not([actiontype="switchtab"]):not([actiontype="extension"]) > #urlbar-display-box {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
#urlbar:not([actiontype="switchtab"]) > #urlbar-display-box > #switchtab {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
#urlbar:not([actiontype="extension"]) > #urlbar-display-box > #extension {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -760,7 +760,8 @@
|
||||||
</hbox>
|
</hbox>
|
||||||
</box>
|
</box>
|
||||||
<box id="urlbar-display-box" align="center">
|
<box id="urlbar-display-box" align="center">
|
||||||
<label class="urlbar-display urlbar-display-switchtab" value="&urlbar.switchToTab.label;"/>
|
<label id="switchtab" class="urlbar-display urlbar-display-switchtab" value="&urlbar.switchToTab.label;"/>
|
||||||
|
<label id="extension" class="urlbar-display urlbar-display-extension" value="&urlbar.extension.label;"/>
|
||||||
</box>
|
</box>
|
||||||
<hbox id="urlbar-icons">
|
<hbox id="urlbar-icons">
|
||||||
<image id="page-report-button"
|
<image id="page-report-button"
|
||||||
|
|
|
@ -56,6 +56,11 @@ file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||||
<field name="AppConstants" readonly="true">
|
<field name="AppConstants" readonly="true">
|
||||||
(Components.utils.import("resource://gre/modules/AppConstants.jsm", {})).AppConstants;
|
(Components.utils.import("resource://gre/modules/AppConstants.jsm", {})).AppConstants;
|
||||||
</field>
|
</field>
|
||||||
|
|
||||||
|
<field name="ExtensionSearchHandler" readonly="true">
|
||||||
|
(Components.utils.import("resource://gre/modules/ExtensionSearchHandler.jsm", {})).ExtensionSearchHandler;
|
||||||
|
</field>
|
||||||
|
|
||||||
<constructor><![CDATA[
|
<constructor><![CDATA[
|
||||||
this._prefs = Components.classes["@mozilla.org/preferences-service;1"]
|
this._prefs = Components.classes["@mozilla.org/preferences-service;1"]
|
||||||
.getService(Components.interfaces.nsIPrefService)
|
.getService(Components.interfaces.nsIPrefService)
|
||||||
|
@ -174,6 +179,10 @@ file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||||
returnValue = action.params.input;
|
returnValue = action.params.input;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
case "extension": {
|
||||||
|
returnValue = action.params.content;
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
let originalUrl = ReaderMode.getOriginalUrl(aValue);
|
let originalUrl = ReaderMode.getOriginalUrl(aValue);
|
||||||
|
@ -478,6 +487,13 @@ file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||||
actionDetails
|
actionDetails
|
||||||
);
|
);
|
||||||
break;
|
break;
|
||||||
|
case "extension":
|
||||||
|
this.handleRevert();
|
||||||
|
// Give the extension control of handling the command.
|
||||||
|
let searchString = action.params.content;
|
||||||
|
let keyword = action.params.keyword;
|
||||||
|
this.ExtensionSearchHandler.handleInputEntered(keyword, searchString, where);
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// This is a fallback for add-ons and old testing code that directly
|
// This is a fallback for add-ons and old testing code that directly
|
||||||
|
@ -595,7 +611,7 @@ file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||||
engineOrEngineName;
|
engineOrEngineName;
|
||||||
let isOneOff = this.popup.oneOffSearchButtons
|
let isOneOff = this.popup.oneOffSearchButtons
|
||||||
.maybeRecordTelemetry(event, openUILinkWhere, openUILinkParams);
|
.maybeRecordTelemetry(event, openUILinkWhere, openUILinkParams);
|
||||||
// Infer the type of the even which triggered the search.
|
// Infer the type of the event which triggered the search.
|
||||||
let eventType = "unknown";
|
let eventType = "unknown";
|
||||||
if (event instanceof KeyboardEvent) {
|
if (event instanceof KeyboardEvent) {
|
||||||
eventType = "key";
|
eventType = "key";
|
||||||
|
@ -1173,6 +1189,9 @@ file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||||
this._clearNoActions();
|
this._clearNoActions();
|
||||||
this.formatValue();
|
this.formatValue();
|
||||||
}
|
}
|
||||||
|
if (ExtensionSearchHandler.hasActiveInputSession()) {
|
||||||
|
ExtensionSearchHandler.handleInputCancelled();
|
||||||
|
}
|
||||||
]]></handler>
|
]]></handler>
|
||||||
|
|
||||||
<handler event="dragstart" phase="capturing"><![CDATA[
|
<handler event="dragstart" phase="capturing"><![CDATA[
|
||||||
|
|
|
@ -0,0 +1,32 @@
|
||||||
|
/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */
|
||||||
|
/* vim: set sts=2 sw=2 et tw=80: */
|
||||||
|
"use strict";
|
||||||
|
|
||||||
|
Cu.import("resource://gre/modules/ExtensionUtils.jsm");
|
||||||
|
|
||||||
|
var {
|
||||||
|
runSafeSyncWithoutClone,
|
||||||
|
SingletonEventManager,
|
||||||
|
} = ExtensionUtils;
|
||||||
|
|
||||||
|
extensions.registerSchemaAPI("omnibox", "addon_child", context => {
|
||||||
|
return {
|
||||||
|
omnibox: {
|
||||||
|
onInputChanged: new SingletonEventManager(context, "omnibox.onInputChanged", fire => {
|
||||||
|
let listener = (text, id) => {
|
||||||
|
runSafeSyncWithoutClone(fire, text, suggestions => {
|
||||||
|
// TODO: Switch to using callParentFunctionNoReturn once bug 1314903 is fixed.
|
||||||
|
context.childManager.callParentAsyncFunction("omnibox_internal.addSuggestions", [
|
||||||
|
id,
|
||||||
|
suggestions,
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
context.childManager.getParentEvent("omnibox_internal.onInputChanged").addListener(listener);
|
||||||
|
return () => {
|
||||||
|
context.childManager.getParentEvent("omnibox_internal.onInputChanged").removeListener(listener);
|
||||||
|
};
|
||||||
|
}).api(),
|
||||||
|
},
|
||||||
|
};
|
||||||
|
});
|
|
@ -0,0 +1,104 @@
|
||||||
|
/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */
|
||||||
|
/* vim: set sts=2 sw=2 et tw=80: */
|
||||||
|
"use strict";
|
||||||
|
|
||||||
|
Cu.import("resource://gre/modules/ExtensionUtils.jsm");
|
||||||
|
|
||||||
|
XPCOMUtils.defineLazyModuleGetter(this, "ExtensionSearchHandler",
|
||||||
|
"resource://gre/modules/ExtensionSearchHandler.jsm");
|
||||||
|
var {
|
||||||
|
SingletonEventManager,
|
||||||
|
} = ExtensionUtils;
|
||||||
|
|
||||||
|
// WeakMap[extension -> keyword]
|
||||||
|
let gKeywordMap = new WeakMap();
|
||||||
|
|
||||||
|
/* eslint-disable mozilla/balanced-listeners */
|
||||||
|
extensions.on("manifest_omnibox", (type, directive, extension, manifest) => {
|
||||||
|
let keyword = manifest.omnibox.keyword;
|
||||||
|
try {
|
||||||
|
// This will throw if the keyword is already registered.
|
||||||
|
ExtensionSearchHandler.registerKeyword(keyword, extension);
|
||||||
|
gKeywordMap.set(extension, keyword);
|
||||||
|
} catch (e) {
|
||||||
|
extension.manifestError(e.message);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
extensions.on("shutdown", (type, extension) => {
|
||||||
|
let keyword = gKeywordMap.get(extension);
|
||||||
|
if (keyword) {
|
||||||
|
ExtensionSearchHandler.unregisterKeyword(keyword);
|
||||||
|
gKeywordMap.delete(extension);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
/* eslint-enable mozilla/balanced-listeners */
|
||||||
|
|
||||||
|
extensions.registerSchemaAPI("omnibox", "addon_parent", context => {
|
||||||
|
let {extension} = context;
|
||||||
|
return {
|
||||||
|
omnibox: {
|
||||||
|
setDefaultSuggestion(suggestion) {
|
||||||
|
let keyword = gKeywordMap.get(extension);
|
||||||
|
try {
|
||||||
|
// This will throw if the keyword failed to register.
|
||||||
|
ExtensionSearchHandler.setDefaultSuggestion(keyword, suggestion);
|
||||||
|
} catch (e) {
|
||||||
|
return Promise.reject(e.message);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
onInputStarted: new SingletonEventManager(context, "omnibox.onInputStarted", fire => {
|
||||||
|
let listener = (eventName) => {
|
||||||
|
fire();
|
||||||
|
};
|
||||||
|
extension.on(ExtensionSearchHandler.MSG_INPUT_STARTED, listener);
|
||||||
|
return () => {
|
||||||
|
extension.off(ExtensionSearchHandler.MSG_INPUT_STARTED, listener);
|
||||||
|
};
|
||||||
|
}).api(),
|
||||||
|
|
||||||
|
onInputCancelled: new SingletonEventManager(context, "omnibox.onInputCancelled", fire => {
|
||||||
|
let listener = (eventName) => {
|
||||||
|
fire();
|
||||||
|
};
|
||||||
|
extension.on(ExtensionSearchHandler.MSG_INPUT_CANCELLED, listener);
|
||||||
|
return () => {
|
||||||
|
extension.off(ExtensionSearchHandler.MSG_INPUT_CANCELLED, listener);
|
||||||
|
};
|
||||||
|
}).api(),
|
||||||
|
|
||||||
|
onInputEntered: new SingletonEventManager(context, "omnibox.onInputEntered", fire => {
|
||||||
|
let listener = (eventName, text, disposition) => {
|
||||||
|
fire(text, disposition);
|
||||||
|
};
|
||||||
|
extension.on(ExtensionSearchHandler.MSG_INPUT_ENTERED, listener);
|
||||||
|
return () => {
|
||||||
|
extension.off(ExtensionSearchHandler.MSG_INPUT_ENTERED, listener);
|
||||||
|
};
|
||||||
|
}).api(),
|
||||||
|
},
|
||||||
|
|
||||||
|
omnibox_internal: {
|
||||||
|
addSuggestions(id, suggestions) {
|
||||||
|
let keyword = gKeywordMap.get(extension);
|
||||||
|
try {
|
||||||
|
ExtensionSearchHandler.addSuggestions(keyword, id, suggestions);
|
||||||
|
} catch (e) {
|
||||||
|
// Silently fail because the extension developer can not know for sure if the user
|
||||||
|
// has already invalidated the callback when asynchronously providing suggestions.
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
onInputChanged: new SingletonEventManager(context, "omnibox_internal.onInputChanged", fire => {
|
||||||
|
let listener = (eventName, text, id) => {
|
||||||
|
fire(text, id);
|
||||||
|
};
|
||||||
|
extension.on(ExtensionSearchHandler.MSG_INPUT_CHANGED, listener);
|
||||||
|
return () => {
|
||||||
|
extension.off(ExtensionSearchHandler.MSG_INPUT_CHANGED, listener);
|
||||||
|
};
|
||||||
|
}).api(),
|
||||||
|
},
|
||||||
|
};
|
||||||
|
});
|
|
@ -5,6 +5,7 @@ category webextension-scripts commands chrome://browser/content/ext-commands.js
|
||||||
category webextension-scripts contextMenus chrome://browser/content/ext-contextMenus.js
|
category webextension-scripts contextMenus chrome://browser/content/ext-contextMenus.js
|
||||||
category webextension-scripts desktop-runtime chrome://browser/content/ext-desktop-runtime.js
|
category webextension-scripts desktop-runtime chrome://browser/content/ext-desktop-runtime.js
|
||||||
category webextension-scripts history chrome://browser/content/ext-history.js
|
category webextension-scripts history chrome://browser/content/ext-history.js
|
||||||
|
category webextension-scripts omnibox chrome://browser/content/ext-omnibox.js
|
||||||
category webextension-scripts pageAction chrome://browser/content/ext-pageAction.js
|
category webextension-scripts pageAction chrome://browser/content/ext-pageAction.js
|
||||||
category webextension-scripts sessions chrome://browser/content/ext-sessions.js
|
category webextension-scripts sessions chrome://browser/content/ext-sessions.js
|
||||||
category webextension-scripts tabs chrome://browser/content/ext-tabs.js
|
category webextension-scripts tabs chrome://browser/content/ext-tabs.js
|
||||||
|
@ -13,6 +14,7 @@ category webextension-scripts windows chrome://browser/content/ext-windows.js
|
||||||
|
|
||||||
# scripts that must run in the same process as addon code.
|
# scripts that must run in the same process as addon code.
|
||||||
category webextension-scripts-addon contextMenus chrome://browser/content/ext-c-contextMenus.js
|
category webextension-scripts-addon contextMenus chrome://browser/content/ext-c-contextMenus.js
|
||||||
|
category webextension-scripts-addon omnibox chrome://browser/content/ext-c-omnibox.js
|
||||||
category webextension-scripts-addon tabs chrome://browser/content/ext-c-tabs.js
|
category webextension-scripts-addon tabs chrome://browser/content/ext-c-tabs.js
|
||||||
|
|
||||||
# schemas
|
# schemas
|
||||||
|
@ -22,6 +24,7 @@ category webextension-schemas commands chrome://browser/content/schemas/commands
|
||||||
category webextension-schemas context_menus chrome://browser/content/schemas/context_menus.json
|
category webextension-schemas context_menus chrome://browser/content/schemas/context_menus.json
|
||||||
category webextension-schemas context_menus_internal chrome://browser/content/schemas/context_menus_internal.json
|
category webextension-schemas context_menus_internal chrome://browser/content/schemas/context_menus_internal.json
|
||||||
category webextension-schemas history chrome://browser/content/schemas/history.json
|
category webextension-schemas history chrome://browser/content/schemas/history.json
|
||||||
|
category webextension-schemas omnibox chrome://browser/content/schemas/omnibox.json
|
||||||
category webextension-schemas page_action chrome://browser/content/schemas/page_action.json
|
category webextension-schemas page_action chrome://browser/content/schemas/page_action.json
|
||||||
category webextension-schemas sessions chrome://browser/content/schemas/sessions.json
|
category webextension-schemas sessions chrome://browser/content/schemas/sessions.json
|
||||||
category webextension-schemas tabs chrome://browser/content/schemas/tabs.json
|
category webextension-schemas tabs chrome://browser/content/schemas/tabs.json
|
||||||
|
|
|
@ -18,10 +18,12 @@ browser.jar:
|
||||||
content/browser/ext-contextMenus.js
|
content/browser/ext-contextMenus.js
|
||||||
content/browser/ext-desktop-runtime.js
|
content/browser/ext-desktop-runtime.js
|
||||||
content/browser/ext-history.js
|
content/browser/ext-history.js
|
||||||
|
content/browser/ext-omnibox.js
|
||||||
content/browser/ext-pageAction.js
|
content/browser/ext-pageAction.js
|
||||||
content/browser/ext-sessions.js
|
content/browser/ext-sessions.js
|
||||||
content/browser/ext-tabs.js
|
content/browser/ext-tabs.js
|
||||||
content/browser/ext-utils.js
|
content/browser/ext-utils.js
|
||||||
content/browser/ext-windows.js
|
content/browser/ext-windows.js
|
||||||
content/browser/ext-c-contextMenus.js
|
content/browser/ext-c-contextMenus.js
|
||||||
|
content/browser/ext-c-omnibox.js
|
||||||
content/browser/ext-c-tabs.js
|
content/browser/ext-c-tabs.js
|
||||||
|
|
|
@ -9,6 +9,7 @@ browser.jar:
|
||||||
content/browser/schemas/context_menus.json
|
content/browser/schemas/context_menus.json
|
||||||
content/browser/schemas/context_menus_internal.json
|
content/browser/schemas/context_menus_internal.json
|
||||||
content/browser/schemas/history.json
|
content/browser/schemas/history.json
|
||||||
|
content/browser/schemas/omnibox.json
|
||||||
content/browser/schemas/page_action.json
|
content/browser/schemas/page_action.json
|
||||||
content/browser/schemas/sessions.json
|
content/browser/schemas/sessions.json
|
||||||
content/browser/schemas/tabs.json
|
content/browser/schemas/tabs.json
|
||||||
|
|
|
@ -0,0 +1,248 @@
|
||||||
|
// Copyright (c) 2012 The Chromium Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style license that can be
|
||||||
|
// found in the LICENSE file.
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"namespace": "manifest",
|
||||||
|
"types": [
|
||||||
|
{
|
||||||
|
"$extend": "WebExtensionManifest",
|
||||||
|
"properties": {
|
||||||
|
"omnibox": {
|
||||||
|
"type": "object",
|
||||||
|
"additionalProperties": { "$ref": "UnrecognizedProperty" },
|
||||||
|
"properties": {
|
||||||
|
"keyword": {
|
||||||
|
"type": "string",
|
||||||
|
"pattern": "^[^?\\s:]([^\\s:]*[^/\\s:])?$"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"namespace": "omnibox",
|
||||||
|
"description": "The omnibox API allows you to register a keyword with Firefox's address bar.",
|
||||||
|
"permissions": ["manifest:omnibox"],
|
||||||
|
"types": [
|
||||||
|
{
|
||||||
|
"id": "DescriptionStyleType",
|
||||||
|
"type": "string",
|
||||||
|
"description": "The style type.",
|
||||||
|
"enum": ["url", "match", "dim"]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "OnInputEnteredDisposition",
|
||||||
|
"type": "string",
|
||||||
|
"enum": ["currentTab", "newForegroundTab", "newBackgroundTab"],
|
||||||
|
"description": "The window disposition for the omnibox query. This is the recommended context to display results. For example, if the omnibox command is to navigate to a certain URL, a disposition of 'newForegroundTab' means the navigation should take place in a new selected tab."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "SuggestResult",
|
||||||
|
"type": "object",
|
||||||
|
"description": "A suggest result.",
|
||||||
|
"properties": {
|
||||||
|
"content": {
|
||||||
|
"type": "string",
|
||||||
|
"minLength": 1,
|
||||||
|
"description": "The text that is put into the URL bar, and that is sent to the extension when the user chooses this entry."
|
||||||
|
},
|
||||||
|
"description": {
|
||||||
|
"type": "string",
|
||||||
|
"minLength": 1,
|
||||||
|
"description": "The text that is displayed in the URL dropdown. Can contain XML-style markup for styling. The supported tags are 'url' (for a literal URL), 'match' (for highlighting text that matched what the user's query), and 'dim' (for dim helper text). The styles can be nested, eg. <dim><match>dimmed match</match></dim>. You must escape the five predefined entities to display them as text: stackoverflow.com/a/1091953/89484 "
|
||||||
|
},
|
||||||
|
"descriptionStyles": {
|
||||||
|
"optional": true,
|
||||||
|
"unsupported": true,
|
||||||
|
"type": "array",
|
||||||
|
"description": "An array of style ranges for the description, as provided by the extension.",
|
||||||
|
"items": {
|
||||||
|
"type": "object",
|
||||||
|
"description": "The style ranges for the description, as provided by the extension.",
|
||||||
|
"properties": {
|
||||||
|
"offset": { "type": "integer" },
|
||||||
|
"type": { "description": "The style type", "$ref": "DescriptionStyleType"},
|
||||||
|
"length": { "type": "integer", "optional": true }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"descriptionStylesRaw": {
|
||||||
|
"optional": true,
|
||||||
|
"unsupported": true,
|
||||||
|
"type": "array",
|
||||||
|
"description": "An array of style ranges for the description, as provided by ToValue().",
|
||||||
|
"items": {
|
||||||
|
"type": "object",
|
||||||
|
"description": "The style ranges for the description, as provided by ToValue().",
|
||||||
|
"properties": {
|
||||||
|
"offset": { "type": "integer" },
|
||||||
|
"type": { "type": "integer" }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "DefaultSuggestResult",
|
||||||
|
"type": "object",
|
||||||
|
"description": "A suggest result.",
|
||||||
|
"properties": {
|
||||||
|
"description": {
|
||||||
|
"type": "string",
|
||||||
|
"minLength": 1,
|
||||||
|
"description": "The text that is displayed in the URL dropdown."
|
||||||
|
},
|
||||||
|
"descriptionStyles": {
|
||||||
|
"optional": true,
|
||||||
|
"unsupported": true,
|
||||||
|
"type": "array",
|
||||||
|
"description": "An array of style ranges for the description, as provided by the extension.",
|
||||||
|
"items": {
|
||||||
|
"type": "object",
|
||||||
|
"description": "The style ranges for the description, as provided by the extension.",
|
||||||
|
"properties": {
|
||||||
|
"offset": { "type": "integer" },
|
||||||
|
"type": { "description": "The style type", "$ref": "DescriptionStyleType"},
|
||||||
|
"length": { "type": "integer", "optional": true }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"descriptionStylesRaw": {
|
||||||
|
"optional": true,
|
||||||
|
"unsupported": true,
|
||||||
|
"type": "array",
|
||||||
|
"description": "An array of style ranges for the description, as provided by ToValue().",
|
||||||
|
"items": {
|
||||||
|
"type": "object",
|
||||||
|
"description": "The style ranges for the description, as provided by ToValue().",
|
||||||
|
"properties": {
|
||||||
|
"offset": { "type": "integer" },
|
||||||
|
"type": { "type": "integer" }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"functions": [
|
||||||
|
{
|
||||||
|
"name": "setDefaultSuggestion",
|
||||||
|
"type": "function",
|
||||||
|
"description": "Sets the description and styling for the default suggestion. The default suggestion is the text that is displayed in the first suggestion row underneath the URL bar.",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"name": "suggestion",
|
||||||
|
"$ref": "DefaultSuggestResult",
|
||||||
|
"description": "A partial SuggestResult object, without the 'content' parameter."
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"events": [
|
||||||
|
{
|
||||||
|
"name": "onInputStarted",
|
||||||
|
"type": "function",
|
||||||
|
"description": "User has started a keyword input session by typing the extension's keyword. This is guaranteed to be sent exactly once per input session, and before any onInputChanged events.",
|
||||||
|
"parameters": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "onInputChanged",
|
||||||
|
"type": "function",
|
||||||
|
"description": "User has changed what is typed into the omnibox.",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"type": "string",
|
||||||
|
"name": "text"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "suggest",
|
||||||
|
"type": "function",
|
||||||
|
"description": "A callback passed to the onInputChanged event used for sending suggestions back to the browser.",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"name": "suggestResults",
|
||||||
|
"type": "array",
|
||||||
|
"description": "Array of suggest results",
|
||||||
|
"items": {
|
||||||
|
"$ref": "SuggestResult"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "onInputEntered",
|
||||||
|
"type": "function",
|
||||||
|
"description": "User has accepted what is typed into the omnibox.",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"type": "string",
|
||||||
|
"name": "text"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "disposition",
|
||||||
|
"$ref": "OnInputEnteredDisposition"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "onInputCancelled",
|
||||||
|
"type": "function",
|
||||||
|
"description": "User has ended the keyword input session without accepting the input.",
|
||||||
|
"parameters": []
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"namespace": "omnibox_internal",
|
||||||
|
"description": "The internal namespace used by the omnibox API.",
|
||||||
|
"defaultContexts": ["addon_parent_only"],
|
||||||
|
"functions": [
|
||||||
|
{
|
||||||
|
"name": "addSuggestions",
|
||||||
|
"type": "function",
|
||||||
|
"async": "callback",
|
||||||
|
"description": "Internal function used by omnibox.onInputChanged for adding search suggestions",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"name": "id",
|
||||||
|
"type": "integer",
|
||||||
|
"description": "The ID of the callback received by onInputChangedInternal"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "suggestResults",
|
||||||
|
"type": "array",
|
||||||
|
"description": "Array of suggest results",
|
||||||
|
"items": {
|
||||||
|
"$ref": "omnibox.SuggestResult"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "function",
|
||||||
|
"name": "callback",
|
||||||
|
"optional": true,
|
||||||
|
"parameters": []
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"events": [
|
||||||
|
{
|
||||||
|
"name": "onInputChanged",
|
||||||
|
"type": "function",
|
||||||
|
"description": "Identical to omnibox.onInputChanged except no 'suggest' callback is provided.",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"type": "string",
|
||||||
|
"name": "text"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
|
@ -45,6 +45,7 @@ tags = webextensions
|
||||||
[browser_ext_incognito_popup.js]
|
[browser_ext_incognito_popup.js]
|
||||||
[browser_ext_lastError.js]
|
[browser_ext_lastError.js]
|
||||||
[browser_ext_legacy_extension_context_contentscript.js]
|
[browser_ext_legacy_extension_context_contentscript.js]
|
||||||
|
[browser_ext_omnibox.js]
|
||||||
[browser_ext_optionsPage_privileges.js]
|
[browser_ext_optionsPage_privileges.js]
|
||||||
[browser_ext_pageAction_context.js]
|
[browser_ext_pageAction_context.js]
|
||||||
[browser_ext_pageAction_popup.js]
|
[browser_ext_pageAction_popup.js]
|
||||||
|
|
|
@ -0,0 +1,286 @@
|
||||||
|
/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */
|
||||||
|
/* vim: set sts=2 sw=2 et tw=80: */
|
||||||
|
"use strict";
|
||||||
|
|
||||||
|
function* setup() {
|
||||||
|
const SUGGEST_URLBAR_PREF = "browser.urlbar.suggest.searches";
|
||||||
|
Services.prefs.setBoolPref(SUGGEST_URLBAR_PREF, false);
|
||||||
|
|
||||||
|
registerCleanupFunction(() => {
|
||||||
|
Services.prefs.clearUserPref(SUGGEST_URLBAR_PREF);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
add_task(function* () {
|
||||||
|
let keyword = "test";
|
||||||
|
|
||||||
|
let extension = ExtensionTestUtils.loadExtension({
|
||||||
|
manifest: {
|
||||||
|
"omnibox": {
|
||||||
|
"keyword": keyword,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
background: function() {
|
||||||
|
browser.omnibox.onInputStarted.addListener(() => {
|
||||||
|
browser.test.sendMessage("on-input-started-fired");
|
||||||
|
});
|
||||||
|
|
||||||
|
let synchronous = true;
|
||||||
|
let suggestions = null;
|
||||||
|
let suggestCallback = null;
|
||||||
|
|
||||||
|
browser.omnibox.onInputChanged.addListener((text, suggest) => {
|
||||||
|
if (synchronous && suggestions) {
|
||||||
|
suggest(suggestions);
|
||||||
|
} else {
|
||||||
|
suggestCallback = suggest;
|
||||||
|
}
|
||||||
|
browser.test.sendMessage("on-input-changed-fired", {text});
|
||||||
|
});
|
||||||
|
|
||||||
|
browser.omnibox.onInputCancelled.addListener(() => {
|
||||||
|
browser.test.sendMessage("on-input-cancelled-fired");
|
||||||
|
});
|
||||||
|
|
||||||
|
browser.omnibox.onInputEntered.addListener((text, disposition) => {
|
||||||
|
browser.test.sendMessage("on-input-entered-fired", {text, disposition});
|
||||||
|
});
|
||||||
|
|
||||||
|
browser.test.onMessage.addListener((msg, data) => {
|
||||||
|
switch (msg) {
|
||||||
|
case "set-suggestions":
|
||||||
|
suggestions = data.suggestions;
|
||||||
|
browser.test.sendMessage("suggestions-set");
|
||||||
|
break;
|
||||||
|
case "set-default-suggestion":
|
||||||
|
browser.omnibox.setDefaultSuggestion(data.suggestion);
|
||||||
|
browser.test.sendMessage("default-suggestion-set");
|
||||||
|
break;
|
||||||
|
case "set-synchronous":
|
||||||
|
synchronous = data.synchronous;
|
||||||
|
break;
|
||||||
|
case "test-multiple-suggest-calls":
|
||||||
|
suggestions.forEach(suggestion => suggestCallback([suggestion]));
|
||||||
|
browser.test.sendMessage("test-ready");
|
||||||
|
break;
|
||||||
|
case "test-suggestions-after-delay":
|
||||||
|
Promise.resolve().then(() => {
|
||||||
|
suggestCallback(suggestions);
|
||||||
|
browser.test.sendMessage("test-ready");
|
||||||
|
});
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
function* expectEvent(event, expected = {}) {
|
||||||
|
let actual = yield extension.awaitMessage(event);
|
||||||
|
if (expected.text) {
|
||||||
|
is(actual.text, expected.text,
|
||||||
|
`Expected "${event}" to have fired with text: "${expected.text}".`);
|
||||||
|
}
|
||||||
|
if (expected.disposition) {
|
||||||
|
is(actual.disposition, expected.disposition,
|
||||||
|
`Expected "${event}" to have fired with disposition: "${expected.disposition}".`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function* startInputSession() {
|
||||||
|
gURLBar.focus();
|
||||||
|
gURLBar.value = keyword;
|
||||||
|
EventUtils.synthesizeKey(" ", {});
|
||||||
|
yield expectEvent("on-input-started-fired");
|
||||||
|
EventUtils.synthesizeKey("t", {});
|
||||||
|
yield expectEvent("on-input-changed-fired", {text: "t"});
|
||||||
|
return "t";
|
||||||
|
}
|
||||||
|
|
||||||
|
function* testInputEvents() {
|
||||||
|
gURLBar.focus();
|
||||||
|
|
||||||
|
// Start an input session by typing in <keyword><space>.
|
||||||
|
for (let letter of keyword) {
|
||||||
|
EventUtils.synthesizeKey(letter, {});
|
||||||
|
}
|
||||||
|
EventUtils.synthesizeKey(" ", {});
|
||||||
|
yield expectEvent("on-input-started-fired");
|
||||||
|
|
||||||
|
// We should expect input changed events now that the keyword is active.
|
||||||
|
EventUtils.synthesizeKey("b", {});
|
||||||
|
yield expectEvent("on-input-changed-fired", {text: "b"});
|
||||||
|
|
||||||
|
EventUtils.synthesizeKey("c", {});
|
||||||
|
yield expectEvent("on-input-changed-fired", {text: "bc"});
|
||||||
|
|
||||||
|
EventUtils.synthesizeKey("VK_BACK_SPACE", {});
|
||||||
|
yield expectEvent("on-input-changed-fired", {text: "b"});
|
||||||
|
|
||||||
|
// Even though the input is <keyword><space> We should not expect an
|
||||||
|
// input started event to fire since the keyword is active.
|
||||||
|
EventUtils.synthesizeKey("VK_BACK_SPACE", {});
|
||||||
|
yield expectEvent("on-input-changed-fired", {text: ""});
|
||||||
|
|
||||||
|
// Make the keyword inactive by hitting backspace.
|
||||||
|
EventUtils.synthesizeKey("VK_BACK_SPACE", {});
|
||||||
|
yield expectEvent("on-input-cancelled-fired");
|
||||||
|
|
||||||
|
// Activate the keyword by typing a space.
|
||||||
|
// Expect onInputStarted to fire.
|
||||||
|
EventUtils.synthesizeKey(" ", {});
|
||||||
|
yield expectEvent("on-input-started-fired");
|
||||||
|
|
||||||
|
// onInputChanged should fire even if a space is entered.
|
||||||
|
EventUtils.synthesizeKey(" ", {});
|
||||||
|
yield expectEvent("on-input-changed-fired", {text: " "});
|
||||||
|
|
||||||
|
// The active session should cancel if the input blurs.
|
||||||
|
gURLBar.blur();
|
||||||
|
yield expectEvent("on-input-cancelled-fired");
|
||||||
|
}
|
||||||
|
|
||||||
|
function* testHeuristicResult(expectedText, setDefaultSuggestion) {
|
||||||
|
if (setDefaultSuggestion) {
|
||||||
|
extension.sendMessage("set-default-suggestion", {
|
||||||
|
suggestion: {
|
||||||
|
description: expectedText,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
yield extension.awaitMessage("default-suggestion-set");
|
||||||
|
}
|
||||||
|
|
||||||
|
let text = yield startInputSession();
|
||||||
|
|
||||||
|
let item = gURLBar.popup.richlistbox.children[0];
|
||||||
|
|
||||||
|
is(item.getAttribute("title"), expectedText,
|
||||||
|
`Expected heuristic result to have title: "${expectedText}".`);
|
||||||
|
|
||||||
|
is(item.getAttribute("displayurl"), `${keyword} ${text}`,
|
||||||
|
`Expected heuristic result to have displayurl: "${keyword} ${text}".`);
|
||||||
|
|
||||||
|
EventUtils.synthesizeMouseAtCenter(item, {});
|
||||||
|
|
||||||
|
yield expectEvent("on-input-entered-fired", {
|
||||||
|
text,
|
||||||
|
disposition: "currentTab",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function* testDisposition(suggestionIndex, expectedDisposition, expectedText) {
|
||||||
|
yield startInputSession();
|
||||||
|
|
||||||
|
// Select the suggestion.
|
||||||
|
for (let i = 0; i < suggestionIndex; i++) {
|
||||||
|
EventUtils.synthesizeKey("VK_DOWN", {});
|
||||||
|
}
|
||||||
|
|
||||||
|
let item = gURLBar.popup.richlistbox.children[suggestionIndex];
|
||||||
|
if (expectedDisposition == "currentTab") {
|
||||||
|
EventUtils.synthesizeMouseAtCenter(item, {});
|
||||||
|
} else if (expectedDisposition == "newForegroundTab") {
|
||||||
|
EventUtils.synthesizeMouseAtCenter(item, {accelKey: true});
|
||||||
|
} else if (expectedDisposition == "newBackgroundTab") {
|
||||||
|
EventUtils.synthesizeMouseAtCenter(item, {shiftKey: true, accelKey: true});
|
||||||
|
}
|
||||||
|
|
||||||
|
yield expectEvent("on-input-entered-fired", {
|
||||||
|
text: expectedText,
|
||||||
|
disposition: expectedDisposition,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function* testSuggestions(info) {
|
||||||
|
extension.sendMessage("set-synchronous", {synchronous: false});
|
||||||
|
|
||||||
|
function expectSuggestion({content, description}, index) {
|
||||||
|
let item = gURLBar.popup.richlistbox.children[index + 1]; // Skip the heuristic result.
|
||||||
|
|
||||||
|
ok(!!item, "Expected item to exist");
|
||||||
|
is(item.getAttribute("title"), description,
|
||||||
|
`Expected suggestion to have title: "${description}".`);
|
||||||
|
|
||||||
|
is(item.getAttribute("displayurl"), `${keyword} ${content}`,
|
||||||
|
`Expected suggestion to have displayurl: "${keyword} ${content}".`);
|
||||||
|
}
|
||||||
|
|
||||||
|
let text = yield startInputSession();
|
||||||
|
|
||||||
|
extension.sendMessage(info.test);
|
||||||
|
yield extension.awaitMessage("test-ready");
|
||||||
|
|
||||||
|
info.suggestions.forEach(expectSuggestion);
|
||||||
|
|
||||||
|
EventUtils.synthesizeMouseAtCenter(gURLBar.popup.richlistbox.children[0], {});
|
||||||
|
yield expectEvent("on-input-entered-fired", {
|
||||||
|
text,
|
||||||
|
disposition: "currentTab",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
yield setup();
|
||||||
|
yield extension.startup();
|
||||||
|
|
||||||
|
yield testInputEvents();
|
||||||
|
|
||||||
|
// Test the heuristic result with default suggestions.
|
||||||
|
yield testHeuristicResult("Generated extension", false /* setDefaultSuggestion */);
|
||||||
|
yield testHeuristicResult("hello world", true /* setDefaultSuggestion */);
|
||||||
|
yield testHeuristicResult("foo bar", true /* setDefaultSuggestion */);
|
||||||
|
|
||||||
|
let suggestions = [
|
||||||
|
{content: "a", description: "select a"},
|
||||||
|
{content: "b", description: "select b"},
|
||||||
|
{content: "c", description: "select c"},
|
||||||
|
];
|
||||||
|
|
||||||
|
extension.sendMessage("set-suggestions", {suggestions});
|
||||||
|
yield extension.awaitMessage("suggestions-set");
|
||||||
|
|
||||||
|
// Test each suggestion and search disposition.
|
||||||
|
yield testDisposition(1, "currentTab", suggestions[0].content);
|
||||||
|
yield testDisposition(2, "newForegroundTab", suggestions[1].content);
|
||||||
|
yield testDisposition(3, "newBackgroundTab", suggestions[2].content);
|
||||||
|
|
||||||
|
extension.sendMessage("set-suggestions", {suggestions});
|
||||||
|
yield extension.awaitMessage("suggestions-set");
|
||||||
|
|
||||||
|
// Test adding suggestions asynchronously.
|
||||||
|
yield testSuggestions({
|
||||||
|
test: "test-multiple-suggest-calls",
|
||||||
|
skipHeuristic: true,
|
||||||
|
suggestions,
|
||||||
|
});
|
||||||
|
yield testSuggestions({
|
||||||
|
test: "test-suggestions-after-delay",
|
||||||
|
skipHeuristic: true,
|
||||||
|
suggestions,
|
||||||
|
});
|
||||||
|
|
||||||
|
// Start monitoring the console.
|
||||||
|
SimpleTest.waitForExplicitFinish();
|
||||||
|
let waitForConsole = new Promise(resolve => {
|
||||||
|
SimpleTest.monitorConsole(resolve, [{
|
||||||
|
message: new RegExp(`The keyword provided is already registered: "${keyword}"`),
|
||||||
|
}]);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Try registering another extension with the same keyword
|
||||||
|
let extension2 = ExtensionTestUtils.loadExtension({
|
||||||
|
manifest: {
|
||||||
|
"omnibox": {
|
||||||
|
"keyword": keyword,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
yield extension2.startup();
|
||||||
|
|
||||||
|
// Stop monitoring the console and confirm the correct errors are logged.
|
||||||
|
SimpleTest.endMonitorConsole();
|
||||||
|
yield waitForConsole;
|
||||||
|
|
||||||
|
yield extension2.unload();
|
||||||
|
yield extension.unload();
|
||||||
|
});
|
|
@ -10,6 +10,17 @@ XPCOMUtils.defineLazyModuleGetter(this, "PlacesTestUtils",
|
||||||
const SUGGEST_URLBAR_PREF = "browser.urlbar.suggest.searches";
|
const SUGGEST_URLBAR_PREF = "browser.urlbar.suggest.searches";
|
||||||
const TEST_ENGINE_BASENAME = "searchSuggestionEngine.xml";
|
const TEST_ENGINE_BASENAME = "searchSuggestionEngine.xml";
|
||||||
|
|
||||||
|
function* promiseAutocompleteResultPopup(inputText) {
|
||||||
|
gURLBar.focus();
|
||||||
|
gURLBar.value = inputText;
|
||||||
|
gURLBar.controller.startSearch(inputText);
|
||||||
|
yield promisePopupShown(gURLBar.popup);
|
||||||
|
yield BrowserTestUtils.waitForCondition(() => {
|
||||||
|
return gURLBar.controller.searchStatus >=
|
||||||
|
Ci.nsIAutoCompleteController.STATUS_COMPLETE_NO_MATCH;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
function* addBookmark(bookmark) {
|
function* addBookmark(bookmark) {
|
||||||
if (bookmark.keyword) {
|
if (bookmark.keyword) {
|
||||||
yield PlacesUtils.keywords.insert({
|
yield PlacesUtils.keywords.insert({
|
||||||
|
@ -142,17 +153,9 @@ add_task(function* test_webnavigation_urlbar_bookmark_transitions() {
|
||||||
|
|
||||||
yield extension.awaitMessage("ready");
|
yield extension.awaitMessage("ready");
|
||||||
|
|
||||||
gURLBar.focus();
|
yield promiseAutocompleteResultPopup("Bookmark To Click");
|
||||||
gURLBar.value = "Bookmark To Click";
|
|
||||||
gURLBar.controller.startSearch("Bookmark To Click");
|
|
||||||
|
|
||||||
let item;
|
|
||||||
|
|
||||||
yield BrowserTestUtils.waitForCondition(() => {
|
|
||||||
item = gURLBar.popup.richlistbox.getItemAtIndex(1);
|
|
||||||
return item;
|
|
||||||
});
|
|
||||||
|
|
||||||
|
let item = gURLBar.popup.richlistbox.getItemAtIndex(1);
|
||||||
item.click();
|
item.click();
|
||||||
yield extension.awaitFinish("webNavigation.from_address_bar.auto_bookmark");
|
yield extension.awaitFinish("webNavigation.from_address_bar.auto_bookmark");
|
||||||
|
|
||||||
|
@ -195,13 +198,7 @@ add_task(function* test_webnavigation_urlbar_keyword_transition() {
|
||||||
|
|
||||||
yield extension.awaitMessage("ready");
|
yield extension.awaitMessage("ready");
|
||||||
|
|
||||||
gURLBar.focus();
|
yield promiseAutocompleteResultPopup("testkw search");
|
||||||
gURLBar.value = "testkw search";
|
|
||||||
gURLBar.controller.startSearch("testkw search");
|
|
||||||
|
|
||||||
yield BrowserTestUtils.waitForCondition(() => {
|
|
||||||
return gURLBar.popup.input.controller.matchCount;
|
|
||||||
});
|
|
||||||
|
|
||||||
let item = gURLBar.popup.richlistbox.getItemAtIndex(0);
|
let item = gURLBar.popup.richlistbox.getItemAtIndex(0);
|
||||||
item.click();
|
item.click();
|
||||||
|
@ -242,14 +239,7 @@ add_task(function* test_webnavigation_urlbar_search_transitions() {
|
||||||
yield extension.awaitMessage("ready");
|
yield extension.awaitMessage("ready");
|
||||||
|
|
||||||
yield prepareSearchEngine();
|
yield prepareSearchEngine();
|
||||||
|
yield promiseAutocompleteResultPopup("foo");
|
||||||
gURLBar.focus();
|
|
||||||
gURLBar.value = "foo";
|
|
||||||
gURLBar.controller.startSearch("foo");
|
|
||||||
|
|
||||||
yield BrowserTestUtils.waitForCondition(() => {
|
|
||||||
return gURLBar.popup.input.controller.matchCount;
|
|
||||||
});
|
|
||||||
|
|
||||||
let item = gURLBar.popup.richlistbox.getItemAtIndex(0);
|
let item = gURLBar.popup.richlistbox.getItemAtIndex(0);
|
||||||
item.click();
|
item.click();
|
||||||
|
|
|
@ -0,0 +1,61 @@
|
||||||
|
/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */
|
||||||
|
/* vim: set sts=2 sw=2 et tw=80: */
|
||||||
|
"use strict";
|
||||||
|
|
||||||
|
function* testKeyword(params) {
|
||||||
|
let normalized = yield ExtensionTestUtils.normalizeManifest({
|
||||||
|
"omnibox": {
|
||||||
|
"keyword": params.keyword,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
if (params.expectError) {
|
||||||
|
let expectedError = (
|
||||||
|
String.raw`omnibox.keyword: String "${params.keyword}" ` +
|
||||||
|
String.raw`must match /^[^?\s:]([^\s:]*[^/\s:])?$/`
|
||||||
|
);
|
||||||
|
ok(normalized.error.includes(expectedError),
|
||||||
|
`The manifest error ${JSON.stringify(normalized.error)} ` +
|
||||||
|
`must contain ${JSON.stringify(expectedError)}`);
|
||||||
|
} else {
|
||||||
|
equal(normalized.error, undefined, "Should not have an error");
|
||||||
|
equal(normalized.errors.length, 0, "Should not have warnings");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
add_task(function* test_manifest_commands() {
|
||||||
|
// accepted single character keywords
|
||||||
|
yield testKeyword({keyword: "a", expectError: false});
|
||||||
|
yield testKeyword({keyword: "-", expectError: false});
|
||||||
|
yield testKeyword({keyword: "嗨", expectError: false});
|
||||||
|
yield testKeyword({keyword: "*", expectError: false});
|
||||||
|
yield testKeyword({keyword: "/", expectError: false});
|
||||||
|
|
||||||
|
// rejected single character keywords
|
||||||
|
yield testKeyword({keyword: "?", expectError: true});
|
||||||
|
yield testKeyword({keyword: " ", expectError: true});
|
||||||
|
yield testKeyword({keyword: ":", expectError: true});
|
||||||
|
|
||||||
|
// accepted multi-character keywords
|
||||||
|
yield testKeyword({keyword: "aa", expectError: false});
|
||||||
|
yield testKeyword({keyword: "http", expectError: false});
|
||||||
|
yield testKeyword({keyword: "f?a", expectError: false});
|
||||||
|
yield testKeyword({keyword: "fa?", expectError: false});
|
||||||
|
yield testKeyword({keyword: "f/x", expectError: false});
|
||||||
|
yield testKeyword({keyword: "/fx", expectError: false});
|
||||||
|
|
||||||
|
// rejected multi-character keywords
|
||||||
|
yield testKeyword({keyword: " a", expectError: true});
|
||||||
|
yield testKeyword({keyword: "a ", expectError: true});
|
||||||
|
yield testKeyword({keyword: " ", expectError: true});
|
||||||
|
yield testKeyword({keyword: " a ", expectError: true});
|
||||||
|
yield testKeyword({keyword: "?fx", expectError: true});
|
||||||
|
yield testKeyword({keyword: "fx/", expectError: true});
|
||||||
|
yield testKeyword({keyword: "f:x", expectError: true});
|
||||||
|
yield testKeyword({keyword: "fx:", expectError: true});
|
||||||
|
yield testKeyword({keyword: "f x", expectError: true});
|
||||||
|
|
||||||
|
// miscellaneous tests
|
||||||
|
yield testKeyword({keyword: "こんにちは", expectError: false});
|
||||||
|
yield testKeyword({keyword: "http://", expectError: true});
|
||||||
|
});
|
|
@ -7,4 +7,5 @@ tags = webextensions
|
||||||
[test_ext_bookmarks.js]
|
[test_ext_bookmarks.js]
|
||||||
[test_ext_history.js]
|
[test_ext_history.js]
|
||||||
[test_ext_manifest_commands.js]
|
[test_ext_manifest_commands.js]
|
||||||
|
[test_ext_manifest_omnibox.js]
|
||||||
[test_ext_manifest_permissions.js]
|
[test_ext_manifest_permissions.js]
|
||||||
|
|
|
@ -421,6 +421,7 @@ BrowserGlue.prototype = {
|
||||||
tag: 7,
|
tag: 7,
|
||||||
visiturl: 8,
|
visiturl: 8,
|
||||||
remotetab: 9,
|
remotetab: 9,
|
||||||
|
extension: 10,
|
||||||
};
|
};
|
||||||
if (actionType in buckets) {
|
if (actionType in buckets) {
|
||||||
Services.telemetry
|
Services.telemetry
|
||||||
|
|
|
@ -392,6 +392,7 @@ These should match what Safari and other Apple applications use on OS X Lion. --
|
||||||
<!ENTITY openCmd.commandkey "l">
|
<!ENTITY openCmd.commandkey "l">
|
||||||
<!ENTITY urlbar.placeholder2 "Search or enter address">
|
<!ENTITY urlbar.placeholder2 "Search or enter address">
|
||||||
<!ENTITY urlbar.accesskey "d">
|
<!ENTITY urlbar.accesskey "d">
|
||||||
|
<!ENTITY urlbar.extension.label "Extension:">
|
||||||
<!ENTITY urlbar.switchToTab.label "Switch to tab:">
|
<!ENTITY urlbar.switchToTab.label "Switch to tab:">
|
||||||
|
|
||||||
<!ENTITY urlbar.searchSuggestionsNotification.question "Would you like to improve your search experience with suggestions?">
|
<!ENTITY urlbar.searchSuggestionsNotification.question "Would you like to improve your search experience with suggestions?">
|
||||||
|
|
|
@ -79,6 +79,13 @@
|
||||||
height: 16px;
|
height: 16px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#urlbar[actiontype="extension"] > #identity-box > #identity-icon {
|
||||||
|
-moz-image-region: inherit;
|
||||||
|
list-style-image: url(chrome://browser/skin/addons/addon-install-anchor.svg);
|
||||||
|
width: 16px;
|
||||||
|
height: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
/* SHARING ICON */
|
/* SHARING ICON */
|
||||||
|
|
||||||
#sharing-icon {
|
#sharing-icon {
|
||||||
|
|
|
@ -11,9 +11,14 @@ const LAYOUT_ERRORS_L10N =
|
||||||
// displayed below it.
|
// displayed below it.
|
||||||
|
|
||||||
const EXPECTED_PROPERTIES = [
|
const EXPECTED_PROPERTIES = [
|
||||||
|
"background-attachment",
|
||||||
|
"background-clip",
|
||||||
"background-color",
|
"background-color",
|
||||||
|
"background-image",
|
||||||
|
"background-origin",
|
||||||
"background-position-x",
|
"background-position-x",
|
||||||
"background-position-y",
|
"background-position-y",
|
||||||
|
"background-repeat",
|
||||||
"background-size",
|
"background-size",
|
||||||
"border-bottom-left-radius",
|
"border-bottom-left-radius",
|
||||||
"border-bottom-right-radius",
|
"border-bottom-right-radius",
|
||||||
|
|
|
@ -177,7 +177,47 @@ var gTests = [
|
||||||
value(1, '4px', 'replace') ] },
|
value(1, '4px', 'replace') ] },
|
||||||
{ property: 'border-top-width',
|
{ property: 'border-top-width',
|
||||||
values: [ value(0, '3px', 'replace', 'linear'),
|
values: [ value(0, '3px', 'replace', 'linear'),
|
||||||
value(1, '4px', 'replace') ] } ]
|
value(1, '4px', 'replace') ] },
|
||||||
|
{ property: 'border-bottom-style',
|
||||||
|
values: [ value(0, 'dotted', 'replace', 'linear'),
|
||||||
|
value(1, 'dashed', 'replace') ] },
|
||||||
|
{ property: 'border-left-style',
|
||||||
|
values: [ value(0, 'solid', 'replace', 'linear'),
|
||||||
|
value(1, 'solid', 'replace') ] },
|
||||||
|
{ property: 'border-right-style',
|
||||||
|
values: [ value(0, 'dotted', 'replace', 'linear'),
|
||||||
|
value(1, 'dashed', 'replace') ] },
|
||||||
|
{ property: 'border-top-style',
|
||||||
|
values: [ value(0, 'dotted', 'replace', 'linear'),
|
||||||
|
value(1, 'dashed', 'replace') ] },
|
||||||
|
{ property: 'border-image-outset',
|
||||||
|
values: [ value(0, '0 0 0 0', 'replace', 'linear'),
|
||||||
|
value(1, '0 0 0 0', 'replace') ] },
|
||||||
|
{ property: 'border-image-repeat',
|
||||||
|
values: [ value(0, 'stretch stretch', 'replace', 'linear'),
|
||||||
|
value(1, 'stretch stretch', 'replace') ] },
|
||||||
|
{ property: 'border-image-slice',
|
||||||
|
values: [ value(0, '100% 100% 100% 100%',
|
||||||
|
'replace', 'linear'),
|
||||||
|
value(1, '100% 100% 100% 100%', 'replace') ] },
|
||||||
|
{ property: 'border-image-source',
|
||||||
|
values: [ value(0, 'none', 'replace', 'linear'),
|
||||||
|
value(1, 'none', 'replace') ] },
|
||||||
|
{ property: 'border-image-width',
|
||||||
|
values: [ value(0, '1 1 1 1', 'replace', 'linear'),
|
||||||
|
value(1, '1 1 1 1', 'replace') ] },
|
||||||
|
{ property: '-moz-border-bottom-colors',
|
||||||
|
values: [ value(0, 'none', 'replace', 'linear'),
|
||||||
|
value(1, 'none', 'replace') ] },
|
||||||
|
{ property: '-moz-border-left-colors',
|
||||||
|
values: [ value(0, 'none', 'replace', 'linear'),
|
||||||
|
value(1, 'none', 'replace') ] },
|
||||||
|
{ property: '-moz-border-right-colors',
|
||||||
|
values: [ value(0, 'none', 'replace', 'linear'),
|
||||||
|
value(1, 'none', 'replace') ] },
|
||||||
|
{ property: '-moz-border-top-colors',
|
||||||
|
values: [ value(0, 'none', 'replace', 'linear'),
|
||||||
|
value(1, 'none', 'replace') ] } ]
|
||||||
},
|
},
|
||||||
{ desc: 'a property-indexed keyframe where a greater shorthand precedes'
|
{ desc: 'a property-indexed keyframe where a greater shorthand precedes'
|
||||||
+ ' a lesser shorthand',
|
+ ' a lesser shorthand',
|
||||||
|
@ -208,7 +248,47 @@ var gTests = [
|
||||||
value(1, '4px', 'replace') ] },
|
value(1, '4px', 'replace') ] },
|
||||||
{ property: 'border-top-width',
|
{ property: 'border-top-width',
|
||||||
values: [ value(0, '3px', 'replace', 'linear'),
|
values: [ value(0, '3px', 'replace', 'linear'),
|
||||||
value(1, '4px', 'replace') ] } ]
|
value(1, '4px', 'replace') ] },
|
||||||
|
{ property: 'border-bottom-style',
|
||||||
|
values: [ value(0, 'dotted', 'replace', 'linear'),
|
||||||
|
value(1, 'dashed', 'replace') ] },
|
||||||
|
{ property: 'border-left-style',
|
||||||
|
values: [ value(0, 'solid', 'replace', 'linear'),
|
||||||
|
value(1, 'solid', 'replace') ] },
|
||||||
|
{ property: 'border-right-style',
|
||||||
|
values: [ value(0, 'dotted', 'replace', 'linear'),
|
||||||
|
value(1, 'dashed', 'replace') ] },
|
||||||
|
{ property: 'border-top-style',
|
||||||
|
values: [ value(0, 'dotted', 'replace', 'linear'),
|
||||||
|
value(1, 'dashed', 'replace') ] },
|
||||||
|
{ property: 'border-image-outset',
|
||||||
|
values: [ value(0, '0 0 0 0', 'replace', 'linear'),
|
||||||
|
value(1, '0 0 0 0', 'replace') ] },
|
||||||
|
{ property: 'border-image-repeat',
|
||||||
|
values: [ value(0, 'stretch stretch', 'replace', 'linear'),
|
||||||
|
value(1, 'stretch stretch', 'replace') ] },
|
||||||
|
{ property: 'border-image-slice',
|
||||||
|
values: [ value(0, '100% 100% 100% 100%',
|
||||||
|
'replace', 'linear'),
|
||||||
|
value(1, '100% 100% 100% 100%', 'replace') ] },
|
||||||
|
{ property: 'border-image-source',
|
||||||
|
values: [ value(0, 'none', 'replace', 'linear'),
|
||||||
|
value(1, 'none', 'replace') ] },
|
||||||
|
{ property: 'border-image-width',
|
||||||
|
values: [ value(0, '1 1 1 1', 'replace', 'linear'),
|
||||||
|
value(1, '1 1 1 1', 'replace') ] },
|
||||||
|
{ property: '-moz-border-bottom-colors',
|
||||||
|
values: [ value(0, 'none', 'replace', 'linear'),
|
||||||
|
value(1, 'none', 'replace') ] },
|
||||||
|
{ property: '-moz-border-left-colors',
|
||||||
|
values: [ value(0, 'none', 'replace', 'linear'),
|
||||||
|
value(1, 'none', 'replace') ] },
|
||||||
|
{ property: '-moz-border-right-colors',
|
||||||
|
values: [ value(0, 'none', 'replace', 'linear'),
|
||||||
|
value(1, 'none', 'replace') ] },
|
||||||
|
{ property: '-moz-border-top-colors',
|
||||||
|
values: [ value(0, 'none', 'replace', 'linear'),
|
||||||
|
value(1, 'none', 'replace') ] } ]
|
||||||
},
|
},
|
||||||
|
|
||||||
// ---------------------------------------------------------------------
|
// ---------------------------------------------------------------------
|
||||||
|
@ -448,7 +528,47 @@ var gTests = [
|
||||||
value(1, '3px', 'replace') ] },
|
value(1, '3px', 'replace') ] },
|
||||||
{ property: 'border-top-width',
|
{ property: 'border-top-width',
|
||||||
values: [ value(0, '2px', 'replace', 'linear'),
|
values: [ value(0, '2px', 'replace', 'linear'),
|
||||||
value(1, '3px', 'replace') ] } ]
|
value(1, '3px', 'replace') ] },
|
||||||
|
{ property: 'border-bottom-style',
|
||||||
|
values: [ value(0, 'dotted', 'replace', 'linear'),
|
||||||
|
value(1, 'dashed', 'replace') ] },
|
||||||
|
{ property: 'border-left-style',
|
||||||
|
values: [ value(0, 'solid', 'replace', 'linear'),
|
||||||
|
value(1, 'dashed', 'replace') ] },
|
||||||
|
{ property: 'border-right-style',
|
||||||
|
values: [ value(0, 'dotted', 'replace', 'linear'),
|
||||||
|
value(1, 'dashed', 'replace') ] },
|
||||||
|
{ property: 'border-top-style',
|
||||||
|
values: [ value(0, 'dotted', 'replace', 'linear'),
|
||||||
|
value(1, 'dashed', 'replace') ] },
|
||||||
|
{ property: 'border-image-outset',
|
||||||
|
values: [ value(0, '0 0 0 0', 'replace', 'linear'),
|
||||||
|
value(1, '0 0 0 0', 'replace') ] },
|
||||||
|
{ property: 'border-image-repeat',
|
||||||
|
values: [ value(0, 'stretch stretch', 'replace', 'linear'),
|
||||||
|
value(1, 'stretch stretch', 'replace') ] },
|
||||||
|
{ property: 'border-image-slice',
|
||||||
|
values: [ value(0, '100% 100% 100% 100%',
|
||||||
|
'replace', 'linear'),
|
||||||
|
value(1, '100% 100% 100% 100%', 'replace') ] },
|
||||||
|
{ property: 'border-image-source',
|
||||||
|
values: [ value(0, 'none', 'replace', 'linear'),
|
||||||
|
value(1, 'none', 'replace') ] },
|
||||||
|
{ property: 'border-image-width',
|
||||||
|
values: [ value(0, '1 1 1 1', 'replace', 'linear'),
|
||||||
|
value(1, '1 1 1 1', 'replace') ] },
|
||||||
|
{ property: '-moz-border-bottom-colors',
|
||||||
|
values: [ value(0, 'none', 'replace', 'linear'),
|
||||||
|
value(1, 'none', 'replace') ] },
|
||||||
|
{ property: '-moz-border-left-colors',
|
||||||
|
values: [ value(0, 'none', 'replace', 'linear'),
|
||||||
|
value(1, 'none', 'replace') ] },
|
||||||
|
{ property: '-moz-border-right-colors',
|
||||||
|
values: [ value(0, 'none', 'replace', 'linear'),
|
||||||
|
value(1, 'none', 'replace') ] },
|
||||||
|
{ property: '-moz-border-top-colors',
|
||||||
|
values: [ value(0, 'none', 'replace', 'linear'),
|
||||||
|
value(1, 'none', 'replace') ] } ]
|
||||||
},
|
},
|
||||||
{ desc: 'a keyframe sequence where greater shorthand precedes'
|
{ desc: 'a keyframe sequence where greater shorthand precedes'
|
||||||
+ ' lesser shorthand',
|
+ ' lesser shorthand',
|
||||||
|
@ -478,7 +598,47 @@ var gTests = [
|
||||||
value(1, '3px', 'replace') ] },
|
value(1, '3px', 'replace') ] },
|
||||||
{ property: 'border-top-width',
|
{ property: 'border-top-width',
|
||||||
values: [ value(0, '2px', 'replace', 'linear'),
|
values: [ value(0, '2px', 'replace', 'linear'),
|
||||||
value(1, '3px', 'replace') ] } ]
|
value(1, '3px', 'replace') ] },
|
||||||
|
{ property: 'border-bottom-style',
|
||||||
|
values: [ value(0, 'dotted', 'replace', 'linear'),
|
||||||
|
value(1, 'dashed', 'replace') ] },
|
||||||
|
{ property: 'border-left-style',
|
||||||
|
values: [ value(0, 'solid', 'replace', 'linear'),
|
||||||
|
value(1, 'dashed', 'replace') ] },
|
||||||
|
{ property: 'border-right-style',
|
||||||
|
values: [ value(0, 'dotted', 'replace', 'linear'),
|
||||||
|
value(1, 'dashed', 'replace') ] },
|
||||||
|
{ property: 'border-top-style',
|
||||||
|
values: [ value(0, 'dotted', 'replace', 'linear'),
|
||||||
|
value(1, 'dashed', 'replace') ] },
|
||||||
|
{ property: 'border-image-outset',
|
||||||
|
values: [ value(0, '0 0 0 0', 'replace', 'linear'),
|
||||||
|
value(1, '0 0 0 0', 'replace') ] },
|
||||||
|
{ property: 'border-image-repeat',
|
||||||
|
values: [ value(0, 'stretch stretch', 'replace', 'linear'),
|
||||||
|
value(1, 'stretch stretch', 'replace') ] },
|
||||||
|
{ property: 'border-image-slice',
|
||||||
|
values: [ value(0, '100% 100% 100% 100%',
|
||||||
|
'replace', 'linear'),
|
||||||
|
value(1, '100% 100% 100% 100%', 'replace') ] },
|
||||||
|
{ property: 'border-image-source',
|
||||||
|
values: [ value(0, 'none', 'replace', 'linear'),
|
||||||
|
value(1, 'none', 'replace') ] },
|
||||||
|
{ property: 'border-image-width',
|
||||||
|
values: [ value(0, '1 1 1 1', 'replace', 'linear'),
|
||||||
|
value(1, '1 1 1 1', 'replace') ] },
|
||||||
|
{ property: '-moz-border-bottom-colors',
|
||||||
|
values: [ value(0, 'none', 'replace', 'linear'),
|
||||||
|
value(1, 'none', 'replace') ] },
|
||||||
|
{ property: '-moz-border-left-colors',
|
||||||
|
values: [ value(0, 'none', 'replace', 'linear'),
|
||||||
|
value(1, 'none', 'replace') ] },
|
||||||
|
{ property: '-moz-border-right-colors',
|
||||||
|
values: [ value(0, 'none', 'replace', 'linear'),
|
||||||
|
value(1, 'none', 'replace') ] },
|
||||||
|
{ property: '-moz-border-top-colors',
|
||||||
|
values: [ value(0, 'none', 'replace', 'linear'),
|
||||||
|
value(1, 'none', 'replace') ] } ]
|
||||||
},
|
},
|
||||||
|
|
||||||
// ---------------------------------------------------------------------
|
// ---------------------------------------------------------------------
|
||||||
|
|
|
@ -41,6 +41,7 @@ support-files =
|
||||||
mozilla/file_deferred_start.html
|
mozilla/file_deferred_start.html
|
||||||
mozilla/file_disabled_properties.html
|
mozilla/file_disabled_properties.html
|
||||||
mozilla/file_disable_animations_api_core.html
|
mozilla/file_disable_animations_api_core.html
|
||||||
|
mozilla/file_discrete-animations.html
|
||||||
mozilla/file_document-timeline-origin-time-range.html
|
mozilla/file_document-timeline-origin-time-range.html
|
||||||
mozilla/file_hide_and_show.html
|
mozilla/file_hide_and_show.html
|
||||||
mozilla/file_partial_keyframes.html
|
mozilla/file_partial_keyframes.html
|
||||||
|
@ -95,6 +96,7 @@ support-files =
|
||||||
[mozilla/test_deferred_start.html]
|
[mozilla/test_deferred_start.html]
|
||||||
[mozilla/test_disable_animations_api_core.html]
|
[mozilla/test_disable_animations_api_core.html]
|
||||||
[mozilla/test_disabled_properties.html]
|
[mozilla/test_disabled_properties.html]
|
||||||
|
[mozilla/test_discrete-animations.html]
|
||||||
[mozilla/test_document-timeline-origin-time-range.html]
|
[mozilla/test_document-timeline-origin-time-range.html]
|
||||||
[mozilla/test_hide_and_show.html]
|
[mozilla/test_hide_and_show.html]
|
||||||
[mozilla/test_partial_keyframes.html]
|
[mozilla/test_partial_keyframes.html]
|
||||||
|
|
|
@ -0,0 +1,170 @@
|
||||||
|
<!doctype html>
|
||||||
|
<head>
|
||||||
|
<meta charset=utf-8>
|
||||||
|
<title>Test Mozilla-specific discrete animatable properties</title>
|
||||||
|
<script type="application/javascript" src="../testcommon.js"></script>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<script>
|
||||||
|
"use strict";
|
||||||
|
|
||||||
|
const gMozillaSpecificProperties = {
|
||||||
|
"-moz-appearance": {
|
||||||
|
// https://drafts.csswg.org/css-align/#propdef-align-content
|
||||||
|
from: "button",
|
||||||
|
to: "none"
|
||||||
|
},
|
||||||
|
"-moz-border-bottom-colors": {
|
||||||
|
from: "rgb(255, 0, 0) rgb(255, 0, 0) rgb(255, 0, 0) rgb(255, 0, 0)",
|
||||||
|
to: "rgb(0, 255, 0) rgb(0, 255, 0) rgb(0, 255, 0) rgb(0, 255, 0)"
|
||||||
|
},
|
||||||
|
"-moz-border-left-colors": {
|
||||||
|
from: "rgb(255, 0, 0) rgb(255, 0, 0) rgb(255, 0, 0) rgb(255, 0, 0)",
|
||||||
|
to: "rgb(0, 255, 0) rgb(0, 255, 0) rgb(0, 255, 0) rgb(0, 255, 0)"
|
||||||
|
},
|
||||||
|
"-moz-border-right-colors": {
|
||||||
|
from: "rgb(255, 0, 0) rgb(255, 0, 0) rgb(255, 0, 0) rgb(255, 0, 0)",
|
||||||
|
to: "rgb(0, 255, 0) rgb(0, 255, 0) rgb(0, 255, 0) rgb(0, 255, 0)"
|
||||||
|
},
|
||||||
|
"-moz-border-top-colors": {
|
||||||
|
from: "rgb(255, 0, 0) rgb(255, 0, 0) rgb(255, 0, 0) rgb(255, 0, 0)",
|
||||||
|
to: "rgb(0, 255, 0) rgb(0, 255, 0) rgb(0, 255, 0) rgb(0, 255, 0)"
|
||||||
|
},
|
||||||
|
"-moz-box-align": {
|
||||||
|
// https://developer.mozilla.org/en/docs/Web/CSS/box-align
|
||||||
|
from: "center",
|
||||||
|
to: "stretch"
|
||||||
|
},
|
||||||
|
"-moz-box-direction": {
|
||||||
|
// https://developer.mozilla.org/en/docs/Web/CSS/box-direction
|
||||||
|
from: "reverse",
|
||||||
|
to: "normal"
|
||||||
|
},
|
||||||
|
"-moz-box-ordinal-group": {
|
||||||
|
// https://developer.mozilla.org/en/docs/Web/CSS/box-ordinal-group
|
||||||
|
from: "1",
|
||||||
|
to: "5"
|
||||||
|
},
|
||||||
|
"-moz-box-orient": {
|
||||||
|
// https://www.w3.org/TR/css-flexbox-1/
|
||||||
|
from: "horizontal",
|
||||||
|
to: "vertical"
|
||||||
|
},
|
||||||
|
"-moz-box-pack": {
|
||||||
|
// https://www.w3.org/TR/2009/WD-css3-flexbox-20090723/#propdef-box-pack
|
||||||
|
from: "center",
|
||||||
|
to: "end"
|
||||||
|
},
|
||||||
|
"-moz-float-edge": {
|
||||||
|
// https://developer.mozilla.org/en/docs/Web/CSS/-moz-float-edge
|
||||||
|
from: "margin-box",
|
||||||
|
to: "content-box"
|
||||||
|
},
|
||||||
|
"-moz-force-broken-image-icon": {
|
||||||
|
// https://developer.mozilla.org/en/docs/Web/CSS/-moz-force-broken-image-icon
|
||||||
|
from: "1",
|
||||||
|
to: "5"
|
||||||
|
},
|
||||||
|
"image-rendering": {
|
||||||
|
// https://drafts.csswg.org/css-images-3/#propdef-image-rendering
|
||||||
|
from: "-moz-crisp-edges",
|
||||||
|
to: "auto"
|
||||||
|
},
|
||||||
|
"-moz-stack-sizing": {
|
||||||
|
// https://developer.mozilla.org/en/docs/Web/CSS/-moz-stack-sizing
|
||||||
|
from: "ignore",
|
||||||
|
to: "stretch-to-fit"
|
||||||
|
},
|
||||||
|
"-moz-tab-size": {
|
||||||
|
// https://drafts.csswg.org/css-text-3/#propdef-tab-size
|
||||||
|
from: "1",
|
||||||
|
to: "5"
|
||||||
|
},
|
||||||
|
"-moz-text-size-adjust": {
|
||||||
|
// https://drafts.csswg.org/css-size-adjust/#propdef-text-size-adjust
|
||||||
|
from: "none",
|
||||||
|
to: "auto"
|
||||||
|
},
|
||||||
|
"-webkit-text-stroke-width": {
|
||||||
|
// https://compat.spec.whatwg.org/#propdef--webkit-text-stroke-width
|
||||||
|
from: "10px",
|
||||||
|
to: "50px"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (let property in gMozillaSpecificProperties) {
|
||||||
|
const testData = gMozillaSpecificProperties[property];
|
||||||
|
const from = testData.from;
|
||||||
|
const to = testData.to;
|
||||||
|
const idlName = propertyToIDL(property);
|
||||||
|
const keyframes = {};
|
||||||
|
keyframes[idlName] = [from, to];
|
||||||
|
|
||||||
|
test(t => {
|
||||||
|
const div = addDiv(t);
|
||||||
|
const animation = div.animate(keyframes,
|
||||||
|
{ duration: 1000, fill: "both" });
|
||||||
|
testAnimationSamples(animation, idlName,
|
||||||
|
[{ time: 0, expected: from.toLowerCase() },
|
||||||
|
{ time: 499, expected: from.toLowerCase() },
|
||||||
|
{ time: 500, expected: to.toLowerCase() },
|
||||||
|
{ time: 1000, expected: to.toLowerCase() }]);
|
||||||
|
}, property + " should animate between '"
|
||||||
|
+ from + "' and '" + to + "' with linear easing");
|
||||||
|
|
||||||
|
test(function(t) {
|
||||||
|
// Easing: http://cubic-bezier.com/#.68,0,1,.01
|
||||||
|
// With this curve, we don't reach the 50% point until about 95% of
|
||||||
|
// the time has expired.
|
||||||
|
const div = addDiv(t);
|
||||||
|
const animation = div.animate(keyframes,
|
||||||
|
{ duration: 1000, fill: "both",
|
||||||
|
easing: "cubic-bezier(0.68,0,1,0.01)" });
|
||||||
|
testAnimationSamples(animation, idlName,
|
||||||
|
[{ time: 0, expected: from.toLowerCase() },
|
||||||
|
{ time: 940, expected: from.toLowerCase() },
|
||||||
|
{ time: 960, expected: to.toLowerCase() }]);
|
||||||
|
}, property + " should animate between '"
|
||||||
|
+ from + "' and '" + to + "' with effect easing");
|
||||||
|
|
||||||
|
test(function(t) {
|
||||||
|
// Easing: http://cubic-bezier.com/#.68,0,1,.01
|
||||||
|
// With this curve, we don't reach the 50% point until about 95% of
|
||||||
|
// the time has expired.
|
||||||
|
keyframes.easing = "cubic-bezier(0.68,0,1,0.01)";
|
||||||
|
const div = addDiv(t);
|
||||||
|
const animation = div.animate(keyframes,
|
||||||
|
{ duration: 1000, fill: "both" });
|
||||||
|
testAnimationSamples(animation, idlName,
|
||||||
|
[{ time: 0, expected: from.toLowerCase() },
|
||||||
|
{ time: 940, expected: from.toLowerCase() },
|
||||||
|
{ time: 960, expected: to.toLowerCase() }]);
|
||||||
|
}, property + " should animate between '"
|
||||||
|
+ from + "' and '" + to + "' with keyframe easing");
|
||||||
|
}
|
||||||
|
|
||||||
|
function propertyToIDL(property) {
|
||||||
|
var prefixMatch = property.match(/^-(\w+)-/);
|
||||||
|
if (prefixMatch) {
|
||||||
|
var prefix = prefixMatch[1] === "moz" ? "Moz" : prefixMatch[1];
|
||||||
|
property = prefix + property.substring(prefixMatch[0].length - 1);
|
||||||
|
}
|
||||||
|
// https://drafts.csswg.org/cssom/#css-property-to-idl-attribute
|
||||||
|
return property.replace(/-([a-z])/gi, function(str, group) {
|
||||||
|
return group.toUpperCase();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function testAnimationSamples(animation, idlName, testSamples) {
|
||||||
|
const target = animation.effect.target;
|
||||||
|
testSamples.forEach(testSample => {
|
||||||
|
animation.currentTime = testSample.time;
|
||||||
|
assert_equals(getComputedStyle(target)[idlName], testSample.expected,
|
||||||
|
"The value should be " + testSample.expected +
|
||||||
|
" at " + testSample.time + "ms");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
done();
|
||||||
|
</script>
|
||||||
|
</body>
|
|
@ -0,0 +1,18 @@
|
||||||
|
<!doctype html>
|
||||||
|
<meta charset=utf-8>
|
||||||
|
<script src="/resources/testharness.js"></script>
|
||||||
|
<script src="/resources/testharnessreport.js"></script>
|
||||||
|
<div id="log"></div>
|
||||||
|
<script>
|
||||||
|
'use strict';
|
||||||
|
setup({explicit_done: true});
|
||||||
|
SpecialPowers.pushPrefEnv(
|
||||||
|
{ "set": [
|
||||||
|
["dom.animations-api.core.enabled", true],
|
||||||
|
["layout.css.osx-font-smoothing.enabled", true],
|
||||||
|
["layout.css.prefixes.webkit", true]
|
||||||
|
] },
|
||||||
|
function() {
|
||||||
|
window.open("file_discrete-animations.html");
|
||||||
|
});
|
||||||
|
</script>
|
|
@ -0,0 +1,94 @@
|
||||||
|
/* -*- 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 "mozilla/HoldDropJSObjects.h"
|
||||||
|
#include "mozilla/dom/TypedArray.h"
|
||||||
|
#include "mozilla/dom/PoseBinding.h"
|
||||||
|
#include "mozilla/dom/Pose.h"
|
||||||
|
|
||||||
|
namespace mozilla {
|
||||||
|
namespace dom {
|
||||||
|
|
||||||
|
NS_IMPL_CYCLE_COLLECTION_CLASS(Pose)
|
||||||
|
|
||||||
|
NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(Pose)
|
||||||
|
NS_IMPL_CYCLE_COLLECTION_UNLINK(mParent)
|
||||||
|
NS_IMPL_CYCLE_COLLECTION_UNLINK_PRESERVED_WRAPPER
|
||||||
|
tmp->mPosition = nullptr;
|
||||||
|
tmp->mLinearVelocity = nullptr;
|
||||||
|
tmp->mLinearAcceleration = nullptr;
|
||||||
|
tmp->mOrientation = nullptr;
|
||||||
|
tmp->mAngularVelocity = nullptr;
|
||||||
|
tmp->mAngularAcceleration = nullptr;
|
||||||
|
NS_IMPL_CYCLE_COLLECTION_UNLINK_END
|
||||||
|
|
||||||
|
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(Pose)
|
||||||
|
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mParent)
|
||||||
|
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_SCRIPT_OBJECTS
|
||||||
|
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
|
||||||
|
|
||||||
|
NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN(Pose)
|
||||||
|
NS_IMPL_CYCLE_COLLECTION_TRACE_PRESERVED_WRAPPER
|
||||||
|
NS_IMPL_CYCLE_COLLECTION_TRACE_JS_MEMBER_CALLBACK(mPosition)
|
||||||
|
NS_IMPL_CYCLE_COLLECTION_TRACE_JS_MEMBER_CALLBACK(mLinearVelocity)
|
||||||
|
NS_IMPL_CYCLE_COLLECTION_TRACE_JS_MEMBER_CALLBACK(mLinearAcceleration)
|
||||||
|
NS_IMPL_CYCLE_COLLECTION_TRACE_JS_MEMBER_CALLBACK(mOrientation)
|
||||||
|
NS_IMPL_CYCLE_COLLECTION_TRACE_JS_MEMBER_CALLBACK(mAngularVelocity)
|
||||||
|
NS_IMPL_CYCLE_COLLECTION_TRACE_JS_MEMBER_CALLBACK(mAngularAcceleration)
|
||||||
|
NS_IMPL_CYCLE_COLLECTION_TRACE_END
|
||||||
|
|
||||||
|
NS_IMPL_CYCLE_COLLECTION_ROOT_NATIVE(Pose, AddRef)
|
||||||
|
NS_IMPL_CYCLE_COLLECTION_UNROOT_NATIVE(Pose, Release)
|
||||||
|
|
||||||
|
|
||||||
|
Pose::Pose(nsISupports* aParent)
|
||||||
|
: mParent(aParent),
|
||||||
|
mPosition(nullptr),
|
||||||
|
mLinearVelocity(nullptr),
|
||||||
|
mLinearAcceleration(nullptr),
|
||||||
|
mOrientation(nullptr),
|
||||||
|
mAngularVelocity(nullptr),
|
||||||
|
mAngularAcceleration(nullptr)
|
||||||
|
{
|
||||||
|
mozilla::HoldJSObjects(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
Pose::~Pose()
|
||||||
|
{
|
||||||
|
mozilla::DropJSObjects(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
nsISupports*
|
||||||
|
Pose::GetParentObject() const
|
||||||
|
{
|
||||||
|
return mParent;
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
Pose::SetFloat32Array(JSContext* aJSContext, JS::MutableHandle<JSObject*> aRetVal,
|
||||||
|
JS::Heap<JSObject*>& aObj, float* aVal, uint32_t sizeOfVal,
|
||||||
|
bool bCreate, ErrorResult& aRv)
|
||||||
|
{
|
||||||
|
if (bCreate) {
|
||||||
|
aObj = Float32Array::Create(aJSContext, this,
|
||||||
|
sizeOfVal, aVal);
|
||||||
|
if (!aObj) {
|
||||||
|
aRv.NoteJSContextException(aJSContext);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
aRetVal.set(aObj);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* virtual */ JSObject*
|
||||||
|
Pose::WrapObject(JSContext* aJSContext, JS::Handle<JSObject*> aGivenProto)
|
||||||
|
{
|
||||||
|
return PoseBinding::Wrap(aJSContext, this, aGivenProto);
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace dom
|
||||||
|
} // namespace mozilla
|
|
@ -0,0 +1,67 @@
|
||||||
|
/* -*- 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_dom_Pose_h
|
||||||
|
#define mozilla_dom_Pose_h
|
||||||
|
|
||||||
|
#include "nsWrapperCache.h"
|
||||||
|
|
||||||
|
namespace mozilla {
|
||||||
|
namespace dom {
|
||||||
|
|
||||||
|
class Pose : public nsWrapperCache
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
explicit Pose(nsISupports* aParent);
|
||||||
|
|
||||||
|
NS_INLINE_DECL_CYCLE_COLLECTING_NATIVE_REFCOUNTING(Pose)
|
||||||
|
NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_NATIVE_CLASS(Pose)
|
||||||
|
|
||||||
|
nsISupports* GetParentObject() const;
|
||||||
|
|
||||||
|
virtual void GetPosition(JSContext* aJSContext,
|
||||||
|
JS::MutableHandle<JSObject*> aRetval,
|
||||||
|
ErrorResult& aRv) = 0;
|
||||||
|
virtual void GetLinearVelocity(JSContext* aJSContext,
|
||||||
|
JS::MutableHandle<JSObject*> aRetval,
|
||||||
|
ErrorResult& aRv) = 0;
|
||||||
|
virtual void GetLinearAcceleration(JSContext* aJSContext,
|
||||||
|
JS::MutableHandle<JSObject*> aRetval,
|
||||||
|
ErrorResult& aRv) = 0;
|
||||||
|
virtual void GetOrientation(JSContext* aJSContext,
|
||||||
|
JS::MutableHandle<JSObject*> aRetval,
|
||||||
|
ErrorResult& aRv) = 0;
|
||||||
|
virtual void GetAngularVelocity(JSContext* aJSContext,
|
||||||
|
JS::MutableHandle<JSObject*> aRetval,
|
||||||
|
ErrorResult& aRv) = 0;
|
||||||
|
virtual void GetAngularAcceleration(JSContext* aJSContext,
|
||||||
|
JS::MutableHandle<JSObject*> aRetval,
|
||||||
|
ErrorResult& aRv) = 0;
|
||||||
|
|
||||||
|
virtual JSObject* WrapObject(JSContext* aJSContext,
|
||||||
|
JS::Handle<JSObject*> aGivenProto) override;
|
||||||
|
|
||||||
|
protected:
|
||||||
|
virtual ~Pose();
|
||||||
|
|
||||||
|
void SetFloat32Array(JSContext* aJSContext, JS::MutableHandle<JSObject*> aRetVal,
|
||||||
|
JS::Heap<JSObject*>& aObj, float* aVal, uint32_t sizeOfVal,
|
||||||
|
bool bCreate, ErrorResult& aRv);
|
||||||
|
|
||||||
|
nsCOMPtr<nsISupports> mParent;
|
||||||
|
|
||||||
|
JS::Heap<JSObject*> mPosition;
|
||||||
|
JS::Heap<JSObject*> mLinearVelocity;
|
||||||
|
JS::Heap<JSObject*> mLinearAcceleration;
|
||||||
|
JS::Heap<JSObject*> mOrientation;
|
||||||
|
JS::Heap<JSObject*> mAngularVelocity;
|
||||||
|
JS::Heap<JSObject*> mAngularAcceleration;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace dom
|
||||||
|
} // namespace mozilla
|
||||||
|
|
||||||
|
#endif // mozilla_dom_Pose_h
|
|
@ -9,7 +9,6 @@
|
||||||
#include "nsGlobalWindow.h"
|
#include "nsGlobalWindow.h"
|
||||||
#include "nsITimeoutHandler.h"
|
#include "nsITimeoutHandler.h"
|
||||||
#include "nsITimer.h"
|
#include "nsITimer.h"
|
||||||
#include "nsPIDOMWindow.h"
|
|
||||||
|
|
||||||
namespace mozilla {
|
namespace mozilla {
|
||||||
namespace dom {
|
namespace dom {
|
||||||
|
|
|
@ -11,11 +11,13 @@
|
||||||
#include "mozilla/TimeStamp.h"
|
#include "mozilla/TimeStamp.h"
|
||||||
#include "nsCOMPtr.h"
|
#include "nsCOMPtr.h"
|
||||||
#include "nsCycleCollectionParticipant.h"
|
#include "nsCycleCollectionParticipant.h"
|
||||||
|
#include "nsPIDOMWindow.h"
|
||||||
|
|
||||||
class nsGlobalWindow;
|
class nsGlobalWindow;
|
||||||
class nsIPrincipal;
|
class nsIPrincipal;
|
||||||
class nsITimeoutHandler;
|
class nsITimeoutHandler;
|
||||||
class nsITimer;
|
class nsITimer;
|
||||||
|
class nsIEventTarget;
|
||||||
|
|
||||||
namespace mozilla {
|
namespace mozilla {
|
||||||
namespace dom {
|
namespace dom {
|
||||||
|
|
|
@ -201,6 +201,7 @@ EXPORTS.mozilla.dom += [
|
||||||
'NodeInfoInlines.h',
|
'NodeInfoInlines.h',
|
||||||
'NodeIterator.h',
|
'NodeIterator.h',
|
||||||
'PartialSHistory.h',
|
'PartialSHistory.h',
|
||||||
|
'Pose.h',
|
||||||
'ProcessGlobal.h',
|
'ProcessGlobal.h',
|
||||||
'ResponsiveImageSelector.h',
|
'ResponsiveImageSelector.h',
|
||||||
'SameProcessMessageQueue.h',
|
'SameProcessMessageQueue.h',
|
||||||
|
@ -345,6 +346,7 @@ UNIFIED_SOURCES += [
|
||||||
'nsXMLContentSerializer.cpp',
|
'nsXMLContentSerializer.cpp',
|
||||||
'nsXMLNameSpaceMap.cpp',
|
'nsXMLNameSpaceMap.cpp',
|
||||||
'PartialSHistory.cpp',
|
'PartialSHistory.cpp',
|
||||||
|
'Pose.cpp',
|
||||||
'PostMessageEvent.cpp',
|
'PostMessageEvent.cpp',
|
||||||
'ProcessGlobal.cpp',
|
'ProcessGlobal.cpp',
|
||||||
'ResponsiveImageSelector.cpp',
|
'ResponsiveImageSelector.cpp',
|
||||||
|
|
|
@ -21,7 +21,7 @@ NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(Gamepad)
|
||||||
NS_INTERFACE_MAP_ENTRY(nsISupports)
|
NS_INTERFACE_MAP_ENTRY(nsISupports)
|
||||||
NS_INTERFACE_MAP_END
|
NS_INTERFACE_MAP_END
|
||||||
|
|
||||||
NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(Gamepad, mParent, mButtons)
|
NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(Gamepad, mParent, mButtons, mPose)
|
||||||
|
|
||||||
void
|
void
|
||||||
Gamepad::UpdateTimestamp()
|
Gamepad::UpdateTimestamp()
|
||||||
|
@ -52,6 +52,7 @@ Gamepad::Gamepad(nsISupports* aParent,
|
||||||
mButtons.InsertElementAt(i, new GamepadButton(mParent));
|
mButtons.InsertElementAt(i, new GamepadButton(mParent));
|
||||||
}
|
}
|
||||||
mAxes.InsertElementsAt(0, aNumAxes, 0.0f);
|
mAxes.InsertElementsAt(0, aNumAxes, 0.0f);
|
||||||
|
mPose = new GamepadPose(aParent);
|
||||||
UpdateTimestamp();
|
UpdateTimestamp();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -87,9 +88,17 @@ Gamepad::SetAxis(uint32_t aAxis, double aValue)
|
||||||
UpdateTimestamp();
|
UpdateTimestamp();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
Gamepad::SetPose(const GamepadPoseState& aPose)
|
||||||
|
{
|
||||||
|
mPose->SetPoseState(aPose);
|
||||||
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
Gamepad::SyncState(Gamepad* aOther)
|
Gamepad::SyncState(Gamepad* aOther)
|
||||||
{
|
{
|
||||||
|
const char* kGamepadExtEnabledPref = "dom.gamepad.extensions.enabled";
|
||||||
|
|
||||||
if (mButtons.Length() != aOther->mButtons.Length() ||
|
if (mButtons.Length() != aOther->mButtons.Length() ||
|
||||||
mAxes.Length() != aOther->mAxes.Length()) {
|
mAxes.Length() != aOther->mAxes.Length()) {
|
||||||
return;
|
return;
|
||||||
|
@ -100,6 +109,7 @@ Gamepad::SyncState(Gamepad* aOther)
|
||||||
mButtons[i]->SetPressed(aOther->mButtons[i]->Pressed());
|
mButtons[i]->SetPressed(aOther->mButtons[i]->Pressed());
|
||||||
mButtons[i]->SetValue(aOther->mButtons[i]->Value());
|
mButtons[i]->SetValue(aOther->mButtons[i]->Value());
|
||||||
}
|
}
|
||||||
|
|
||||||
bool changed = false;
|
bool changed = false;
|
||||||
for (uint32_t i = 0; i < mAxes.Length(); ++i) {
|
for (uint32_t i = 0; i < mAxes.Length(); ++i) {
|
||||||
changed = changed || (mAxes[i] != aOther->mAxes[i]);
|
changed = changed || (mAxes[i] != aOther->mAxes[i]);
|
||||||
|
@ -108,6 +118,12 @@ Gamepad::SyncState(Gamepad* aOther)
|
||||||
if (changed) {
|
if (changed) {
|
||||||
GamepadBinding::ClearCachedAxesValue(this);
|
GamepadBinding::ClearCachedAxesValue(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (Preferences::GetBool(kGamepadExtEnabledPref)) {
|
||||||
|
MOZ_ASSERT(aOther->GetPose());
|
||||||
|
mPose->SetPoseState(aOther->GetPose()->GetPoseState());
|
||||||
|
}
|
||||||
|
|
||||||
UpdateTimestamp();
|
UpdateTimestamp();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -10,6 +10,7 @@
|
||||||
#include "mozilla/ErrorResult.h"
|
#include "mozilla/ErrorResult.h"
|
||||||
#include "mozilla/dom/GamepadBinding.h"
|
#include "mozilla/dom/GamepadBinding.h"
|
||||||
#include "mozilla/dom/GamepadButton.h"
|
#include "mozilla/dom/GamepadButton.h"
|
||||||
|
#include "mozilla/dom/GamepadPose.h"
|
||||||
#include "mozilla/dom/Performance.h"
|
#include "mozilla/dom/Performance.h"
|
||||||
#include <stdint.h>
|
#include <stdint.h>
|
||||||
#include "nsCOMPtr.h"
|
#include "nsCOMPtr.h"
|
||||||
|
@ -49,6 +50,7 @@ public:
|
||||||
void SetButton(uint32_t aButton, bool aPressed, double aValue);
|
void SetButton(uint32_t aButton, bool aPressed, double aValue);
|
||||||
void SetAxis(uint32_t aAxis, double aValue);
|
void SetAxis(uint32_t aAxis, double aValue);
|
||||||
void SetIndex(uint32_t aIndex);
|
void SetIndex(uint32_t aIndex);
|
||||||
|
void SetPose(const GamepadPoseState& aPose);
|
||||||
|
|
||||||
// Make the state of this gamepad equivalent to other.
|
// Make the state of this gamepad equivalent to other.
|
||||||
void SyncState(Gamepad* aOther);
|
void SyncState(Gamepad* aOther);
|
||||||
|
@ -99,6 +101,11 @@ public:
|
||||||
aAxes = mAxes;
|
aAxes = mAxes;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
GamepadPose* GetPose() const
|
||||||
|
{
|
||||||
|
return mPose;
|
||||||
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
virtual ~Gamepad() {}
|
virtual ~Gamepad() {}
|
||||||
void UpdateTimestamp();
|
void UpdateTimestamp();
|
||||||
|
@ -118,6 +125,7 @@ protected:
|
||||||
nsTArray<RefPtr<GamepadButton>> mButtons;
|
nsTArray<RefPtr<GamepadButton>> mButtons;
|
||||||
nsTArray<double> mAxes;
|
nsTArray<double> mAxes;
|
||||||
DOMHighResTimeStamp mTimestamp;
|
DOMHighResTimeStamp mTimestamp;
|
||||||
|
RefPtr<GamepadPose> mPose;
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace dom
|
} // namespace dom
|
||||||
|
|
|
@ -405,6 +405,49 @@ GamepadManager::FireAxisMoveEvent(EventTarget* aTarget,
|
||||||
aTarget->DispatchEvent(event, &defaultActionEnabled);
|
aTarget->DispatchEvent(event, &defaultActionEnabled);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
GamepadManager::NewPoseEvent(uint32_t aIndex, GamepadServiceType aServiceType,
|
||||||
|
const GamepadPoseState& aPose)
|
||||||
|
{
|
||||||
|
if (mShuttingDown) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32_t newIndex = GetGamepadIndexWithServiceType(aIndex, aServiceType);
|
||||||
|
|
||||||
|
RefPtr<Gamepad> gamepad = GetGamepad(newIndex);
|
||||||
|
if (!gamepad) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
gamepad->SetPose(aPose);
|
||||||
|
|
||||||
|
// Hold on to listeners in a separate array because firing events
|
||||||
|
// can mutate the mListeners array.
|
||||||
|
nsTArray<RefPtr<nsGlobalWindow>> listeners(mListeners);
|
||||||
|
MOZ_ASSERT(!listeners.IsEmpty());
|
||||||
|
|
||||||
|
for (uint32_t i = 0; i < listeners.Length(); i++) {
|
||||||
|
|
||||||
|
MOZ_ASSERT(listeners[i]->IsInnerWindow());
|
||||||
|
|
||||||
|
// Only send events to non-background windows
|
||||||
|
if (!listeners[i]->AsInner()->IsCurrentInnerWindow() ||
|
||||||
|
listeners[i]->GetOuterWindow()->IsBackground()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool firstTime = MaybeWindowHasSeenGamepad(listeners[i], newIndex);
|
||||||
|
|
||||||
|
RefPtr<Gamepad> listenerGamepad = listeners[i]->GetGamepad(newIndex);
|
||||||
|
if (listenerGamepad) {
|
||||||
|
listenerGamepad->SetPose(aPose);
|
||||||
|
if (firstTime) {
|
||||||
|
FireConnectionEvent(listeners[i], listenerGamepad, true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
GamepadManager::NewConnectionEvent(uint32_t aIndex, bool aConnected)
|
GamepadManager::NewConnectionEvent(uint32_t aIndex, bool aConnected)
|
||||||
{
|
{
|
||||||
|
@ -611,6 +654,11 @@ GamepadManager::Update(const GamepadChangeEvent& aEvent)
|
||||||
NewAxisMoveEvent(a.index(), a.service_type(), a.axis(), a.value());
|
NewAxisMoveEvent(a.index(), a.service_type(), a.axis(), a.value());
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
if (aEvent.type() == GamepadChangeEvent::TGamepadPoseInformation) {
|
||||||
|
const GamepadPoseInformation& a = aEvent.get_GamepadPoseInformation();
|
||||||
|
NewPoseEvent(a.index(), a.service_type(), a.pose_state());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
MOZ_CRASH("We shouldn't be here!");
|
MOZ_CRASH("We shouldn't be here!");
|
||||||
|
|
||||||
|
|
|
@ -69,6 +69,11 @@ class GamepadManager final : public nsIObserver,
|
||||||
void NewAxisMoveEvent(uint32_t aIndex, GamepadServiceType aServiceType,
|
void NewAxisMoveEvent(uint32_t aIndex, GamepadServiceType aServiceType,
|
||||||
uint32_t aAxis, double aValue);
|
uint32_t aAxis, double aValue);
|
||||||
|
|
||||||
|
// Update the state of |aState| for the gamepad at |aIndex| for all
|
||||||
|
// windows that are listening and visible.
|
||||||
|
void NewPoseEvent(uint32_t aIndex, GamepadServiceType aServiceType,
|
||||||
|
const GamepadPoseState& aState);
|
||||||
|
|
||||||
// Synchronize the state of |aGamepad| to match the gamepad stored at |aIndex|
|
// Synchronize the state of |aGamepad| to match the gamepad stored at |aIndex|
|
||||||
void SyncGamepadState(uint32_t aIndex, Gamepad* aGamepad);
|
void SyncGamepadState(uint32_t aIndex, Gamepad* aGamepad);
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,120 @@
|
||||||
|
/* -*- 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 "nsWrapperCache.h"
|
||||||
|
|
||||||
|
#include "mozilla/HoldDropJSObjects.h"
|
||||||
|
#include "mozilla/dom/GamepadPoseBinding.h"
|
||||||
|
#include "mozilla/dom/GamepadPose.h"
|
||||||
|
|
||||||
|
namespace mozilla {
|
||||||
|
namespace dom {
|
||||||
|
|
||||||
|
GamepadPose::GamepadPose(nsISupports* aParent, const GamepadPoseState& aState)
|
||||||
|
: Pose(aParent),
|
||||||
|
mPoseState(aState)
|
||||||
|
{
|
||||||
|
mozilla::HoldJSObjects(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
GamepadPose::GamepadPose(nsISupports* aParent)
|
||||||
|
: Pose(aParent)
|
||||||
|
{
|
||||||
|
mozilla::HoldJSObjects(this);
|
||||||
|
mPoseState.Clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
GamepadPose::~GamepadPose()
|
||||||
|
{
|
||||||
|
mozilla::DropJSObjects(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* virtual */ JSObject*
|
||||||
|
GamepadPose::WrapObject(JSContext* aJSContext, JS::Handle<JSObject*> aGivenProto)
|
||||||
|
{
|
||||||
|
return GamepadPoseBinding::Wrap(aJSContext, this, aGivenProto);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool
|
||||||
|
GamepadPose::HasOrientation() const
|
||||||
|
{
|
||||||
|
return bool(mPoseState.flags & GamepadCapabilityFlags::Cap_Position);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool
|
||||||
|
GamepadPose::HasPosition() const
|
||||||
|
{
|
||||||
|
return bool(mPoseState.flags & GamepadCapabilityFlags::Cap_Orientation);
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
GamepadPose::GetPosition(JSContext* aJSContext,
|
||||||
|
JS::MutableHandle<JSObject*> aRetval,
|
||||||
|
ErrorResult& aRv)
|
||||||
|
{
|
||||||
|
SetFloat32Array(aJSContext, aRetval, mPosition, mPoseState.position, 3,
|
||||||
|
bool(mPoseState.flags & GamepadCapabilityFlags::Cap_Position), aRv);
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
GamepadPose::GetLinearVelocity(JSContext* aJSContext,
|
||||||
|
JS::MutableHandle<JSObject*> aRetval,
|
||||||
|
ErrorResult& aRv)
|
||||||
|
{
|
||||||
|
SetFloat32Array(aJSContext, aRetval, mLinearVelocity, mPoseState.linearVelocity, 3,
|
||||||
|
bool(mPoseState.flags & GamepadCapabilityFlags::Cap_Position), aRv);
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
GamepadPose::GetLinearAcceleration(JSContext* aJSContext,
|
||||||
|
JS::MutableHandle<JSObject*> aRetval,
|
||||||
|
ErrorResult& aRv)
|
||||||
|
{
|
||||||
|
SetFloat32Array(aJSContext, aRetval, mLinearAcceleration, mPoseState.linearAcceleration, 3,
|
||||||
|
bool(mPoseState.flags & GamepadCapabilityFlags::Cap_LinearAcceleration), aRv);
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
GamepadPose::GetOrientation(JSContext* aJSContext,
|
||||||
|
JS::MutableHandle<JSObject*> aRetval,
|
||||||
|
ErrorResult& aRv)
|
||||||
|
{
|
||||||
|
SetFloat32Array(aJSContext, aRetval, mOrientation, mPoseState.orientation, 4,
|
||||||
|
bool(mPoseState.flags & GamepadCapabilityFlags::Cap_Orientation), aRv);
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
GamepadPose::GetAngularVelocity(JSContext* aJSContext,
|
||||||
|
JS::MutableHandle<JSObject*> aRetval,
|
||||||
|
ErrorResult& aRv)
|
||||||
|
{
|
||||||
|
SetFloat32Array(aJSContext, aRetval, mAngularVelocity, mPoseState.angularVelocity, 3,
|
||||||
|
bool(mPoseState.flags & GamepadCapabilityFlags::Cap_Orientation), aRv);
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
GamepadPose::GetAngularAcceleration(JSContext* aJSContext,
|
||||||
|
JS::MutableHandle<JSObject*> aRetval,
|
||||||
|
ErrorResult& aRv)
|
||||||
|
{
|
||||||
|
SetFloat32Array(aJSContext, aRetval, mAngularAcceleration, mPoseState.angularAcceleration, 3,
|
||||||
|
bool(mPoseState.flags & GamepadCapabilityFlags::Cap_AngularAcceleration), aRv);
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
GamepadPose::SetPoseState(const GamepadPoseState& aPose)
|
||||||
|
{
|
||||||
|
mPoseState = aPose;
|
||||||
|
}
|
||||||
|
|
||||||
|
const GamepadPoseState&
|
||||||
|
GamepadPose::GetPoseState()
|
||||||
|
{
|
||||||
|
return mPoseState;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace dom
|
||||||
|
} // namespace mozilla
|
|
@ -0,0 +1,58 @@
|
||||||
|
/* -*- 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_dom_gamepad_GamepadPose_h
|
||||||
|
#define mozilla_dom_gamepad_GamepadPose_h
|
||||||
|
|
||||||
|
#include "mozilla/TypedEnumBits.h"
|
||||||
|
#include "mozilla/dom/Pose.h"
|
||||||
|
#include "mozilla/dom/GamepadPoseState.h"
|
||||||
|
|
||||||
|
namespace mozilla {
|
||||||
|
namespace dom {
|
||||||
|
|
||||||
|
class GamepadPose final : public Pose
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
GamepadPose(nsISupports* aParent, const GamepadPoseState& aState);
|
||||||
|
explicit GamepadPose(nsISupports* aParent);
|
||||||
|
|
||||||
|
virtual JSObject* WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto) override;
|
||||||
|
|
||||||
|
bool HasOrientation() const;
|
||||||
|
bool HasPosition() const;
|
||||||
|
virtual void GetPosition(JSContext* aJSContext,
|
||||||
|
JS::MutableHandle<JSObject*> aRetval,
|
||||||
|
ErrorResult& aRv) override;
|
||||||
|
virtual void GetLinearVelocity(JSContext* aJSContext,
|
||||||
|
JS::MutableHandle<JSObject*> aRetval,
|
||||||
|
ErrorResult& aRv) override;
|
||||||
|
virtual void GetLinearAcceleration(JSContext* aJSContext,
|
||||||
|
JS::MutableHandle<JSObject*> aRetval,
|
||||||
|
ErrorResult& aRv) override;
|
||||||
|
virtual void GetOrientation(JSContext* aJSContext,
|
||||||
|
JS::MutableHandle<JSObject*> aRetval,
|
||||||
|
ErrorResult& aRv) override;
|
||||||
|
virtual void GetAngularVelocity(JSContext* aJSContext,
|
||||||
|
JS::MutableHandle<JSObject*> aRetval,
|
||||||
|
ErrorResult& aRv) override;
|
||||||
|
virtual void GetAngularAcceleration(JSContext* aJSContext,
|
||||||
|
JS::MutableHandle<JSObject*> aRetval,
|
||||||
|
ErrorResult& aRv) override;
|
||||||
|
void SetPoseState(const GamepadPoseState& aPose);
|
||||||
|
const GamepadPoseState& GetPoseState();
|
||||||
|
|
||||||
|
private:
|
||||||
|
virtual ~GamepadPose();
|
||||||
|
|
||||||
|
nsCOMPtr<nsISupports> mParent;
|
||||||
|
GamepadPoseState mPoseState;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace dom
|
||||||
|
} // namespace mozilla
|
||||||
|
|
||||||
|
#endif // mozilla_dom_gamepad_GamepadPose_h
|
|
@ -0,0 +1,88 @@
|
||||||
|
|
||||||
|
#ifndef mozilla_dom_gamepad_GamepadPoseState_h_
|
||||||
|
#define mozilla_dom_gamepad_GamepadPoseState_h_
|
||||||
|
|
||||||
|
namespace mozilla{
|
||||||
|
namespace dom{
|
||||||
|
|
||||||
|
enum class GamepadCapabilityFlags : uint16_t {
|
||||||
|
Cap_None = 0,
|
||||||
|
/**
|
||||||
|
* Cap_Position is set if the Gamepad is capable of tracking its position.
|
||||||
|
*/
|
||||||
|
Cap_Position = 1 << 1,
|
||||||
|
/**
|
||||||
|
* Cap_Orientation is set if the Gamepad is capable of tracking its orientation.
|
||||||
|
*/
|
||||||
|
Cap_Orientation = 1 << 2,
|
||||||
|
/**
|
||||||
|
* Cap_AngularAcceleration is set if the Gamepad is capable of tracking its
|
||||||
|
* angular acceleration.
|
||||||
|
*/
|
||||||
|
Cap_AngularAcceleration = 1 << 3,
|
||||||
|
/**
|
||||||
|
* Cap_LinearAcceleration is set if the Gamepad is capable of tracking its
|
||||||
|
* linear acceleration.
|
||||||
|
*/
|
||||||
|
Cap_LinearAcceleration = 1 << 4,
|
||||||
|
/**
|
||||||
|
* Cap_All used for validity checking during IPC serialization
|
||||||
|
*/
|
||||||
|
Cap_All = (1 << 5) - 1
|
||||||
|
};
|
||||||
|
|
||||||
|
MOZ_MAKE_ENUM_CLASS_BITWISE_OPERATORS(GamepadCapabilityFlags)
|
||||||
|
|
||||||
|
struct GamepadPoseState
|
||||||
|
{
|
||||||
|
GamepadCapabilityFlags flags;
|
||||||
|
float orientation[4];
|
||||||
|
float position[3];
|
||||||
|
float angularVelocity[3];
|
||||||
|
float angularAcceleration[3];
|
||||||
|
float linearVelocity[3];
|
||||||
|
float linearAcceleration[3];
|
||||||
|
|
||||||
|
GamepadPoseState()
|
||||||
|
{
|
||||||
|
Clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool operator==(const GamepadPoseState& aPose) const
|
||||||
|
{
|
||||||
|
return flags == aPose.flags
|
||||||
|
&& orientation[0] == aPose.orientation[0]
|
||||||
|
&& orientation[1] == aPose.orientation[1]
|
||||||
|
&& orientation[2] == aPose.orientation[2]
|
||||||
|
&& orientation[3] == aPose.orientation[3]
|
||||||
|
&& position[0] == aPose.position[0]
|
||||||
|
&& position[1] == aPose.position[1]
|
||||||
|
&& position[2] == aPose.position[2]
|
||||||
|
&& angularVelocity[0] == aPose.angularVelocity[0]
|
||||||
|
&& angularVelocity[1] == aPose.angularVelocity[1]
|
||||||
|
&& angularVelocity[2] == aPose.angularVelocity[2]
|
||||||
|
&& angularAcceleration[0] == aPose.angularAcceleration[0]
|
||||||
|
&& angularAcceleration[1] == aPose.angularAcceleration[1]
|
||||||
|
&& angularAcceleration[2] == aPose.angularAcceleration[2]
|
||||||
|
&& linearVelocity[0] == aPose.linearVelocity[0]
|
||||||
|
&& linearVelocity[1] == aPose.linearVelocity[1]
|
||||||
|
&& linearVelocity[2] == aPose.linearVelocity[2]
|
||||||
|
&& linearAcceleration[0] == aPose.linearAcceleration[0]
|
||||||
|
&& linearAcceleration[1] == aPose.linearAcceleration[1]
|
||||||
|
&& linearAcceleration[2] == aPose.linearAcceleration[2];
|
||||||
|
}
|
||||||
|
|
||||||
|
bool operator!=(const GamepadPoseState& aPose) const
|
||||||
|
{
|
||||||
|
return !(*this == aPose);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Clear() {
|
||||||
|
memset(this, 0, sizeof(GamepadPoseState));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
}// namespace dom
|
||||||
|
}// namespace mozilla
|
||||||
|
|
||||||
|
#endif // mozilla_dom_gamepad_GamepadPoseState_h_
|
|
@ -3,6 +3,7 @@
|
||||||
* You can obtain one at http://mozilla.org/MPL/2.0/. */
|
* You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||||
|
|
||||||
using mozilla::dom::GamepadServiceType from "mozilla/dom/GamepadMessageUtils.h";
|
using mozilla::dom::GamepadServiceType from "mozilla/dom/GamepadMessageUtils.h";
|
||||||
|
using mozilla::dom::GamepadPoseState from "mozilla/dom/GamepadMessageUtils.h";
|
||||||
|
|
||||||
|
|
||||||
namespace mozilla {
|
namespace mozilla {
|
||||||
|
@ -40,11 +41,18 @@ struct GamepadButtonInformation {
|
||||||
double value;
|
double value;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
struct GamepadPoseInformation {
|
||||||
|
uint32_t index;
|
||||||
|
GamepadServiceType service_type;
|
||||||
|
GamepadPoseState pose_state;
|
||||||
|
};
|
||||||
|
|
||||||
union GamepadChangeEvent {
|
union GamepadChangeEvent {
|
||||||
GamepadAdded;
|
GamepadAdded;
|
||||||
GamepadRemoved;
|
GamepadRemoved;
|
||||||
GamepadAxisInformation;
|
GamepadAxisInformation;
|
||||||
GamepadButtonInformation;
|
GamepadButtonInformation;
|
||||||
|
GamepadPoseInformation;
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace dom
|
} // namespace dom
|
||||||
|
|
|
@ -4,6 +4,7 @@
|
||||||
|
|
||||||
#include "ipc/IPCMessageUtils.h"
|
#include "ipc/IPCMessageUtils.h"
|
||||||
#include "mozilla/dom/GamepadServiceType.h"
|
#include "mozilla/dom/GamepadServiceType.h"
|
||||||
|
#include "mozilla/dom/GamepadPoseState.h"
|
||||||
|
|
||||||
namespace IPC {
|
namespace IPC {
|
||||||
|
|
||||||
|
@ -13,6 +14,69 @@ struct ParamTraits<mozilla::dom::GamepadServiceType> :
|
||||||
mozilla::dom::GamepadServiceType(0),
|
mozilla::dom::GamepadServiceType(0),
|
||||||
mozilla::dom::GamepadServiceType(
|
mozilla::dom::GamepadServiceType(
|
||||||
mozilla::dom::GamepadServiceType::NumGamepadServiceType)> {};
|
mozilla::dom::GamepadServiceType::NumGamepadServiceType)> {};
|
||||||
|
|
||||||
|
template<>
|
||||||
|
struct ParamTraits<mozilla::dom::GamepadCapabilityFlags> :
|
||||||
|
public BitFlagsEnumSerializer<mozilla::dom::GamepadCapabilityFlags,
|
||||||
|
mozilla::dom::GamepadCapabilityFlags::Cap_All> {};
|
||||||
|
|
||||||
|
template <>
|
||||||
|
struct ParamTraits<mozilla::dom::GamepadPoseState>
|
||||||
|
{
|
||||||
|
typedef mozilla::dom::GamepadPoseState paramType;
|
||||||
|
|
||||||
|
static void Write(Message* aMsg, const paramType& aParam)
|
||||||
|
{
|
||||||
|
WriteParam(aMsg, aParam.flags);
|
||||||
|
WriteParam(aMsg, aParam.orientation[0]);
|
||||||
|
WriteParam(aMsg, aParam.orientation[1]);
|
||||||
|
WriteParam(aMsg, aParam.orientation[2]);
|
||||||
|
WriteParam(aMsg, aParam.orientation[3]);
|
||||||
|
WriteParam(aMsg, aParam.position[0]);
|
||||||
|
WriteParam(aMsg, aParam.position[1]);
|
||||||
|
WriteParam(aMsg, aParam.position[2]);
|
||||||
|
WriteParam(aMsg, aParam.angularVelocity[0]);
|
||||||
|
WriteParam(aMsg, aParam.angularVelocity[1]);
|
||||||
|
WriteParam(aMsg, aParam.angularVelocity[2]);
|
||||||
|
WriteParam(aMsg, aParam.angularAcceleration[0]);
|
||||||
|
WriteParam(aMsg, aParam.angularAcceleration[1]);
|
||||||
|
WriteParam(aMsg, aParam.angularAcceleration[2]);
|
||||||
|
WriteParam(aMsg, aParam.linearVelocity[0]);
|
||||||
|
WriteParam(aMsg, aParam.linearVelocity[1]);
|
||||||
|
WriteParam(aMsg, aParam.linearVelocity[2]);
|
||||||
|
WriteParam(aMsg, aParam.linearAcceleration[0]);
|
||||||
|
WriteParam(aMsg, aParam.linearAcceleration[1]);
|
||||||
|
WriteParam(aMsg, aParam.linearAcceleration[2]);
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool Read(const Message* aMsg, PickleIterator* aIter, paramType* aResult)
|
||||||
|
{
|
||||||
|
if (!ReadParam(aMsg, aIter, &(aResult->flags)) ||
|
||||||
|
!ReadParam(aMsg, aIter, &(aResult->orientation[0])) ||
|
||||||
|
!ReadParam(aMsg, aIter, &(aResult->orientation[1])) ||
|
||||||
|
!ReadParam(aMsg, aIter, &(aResult->orientation[2])) ||
|
||||||
|
!ReadParam(aMsg, aIter, &(aResult->orientation[3])) ||
|
||||||
|
!ReadParam(aMsg, aIter, &(aResult->position[0])) ||
|
||||||
|
!ReadParam(aMsg, aIter, &(aResult->position[1])) ||
|
||||||
|
!ReadParam(aMsg, aIter, &(aResult->position[2])) ||
|
||||||
|
!ReadParam(aMsg, aIter, &(aResult->angularVelocity[0])) ||
|
||||||
|
!ReadParam(aMsg, aIter, &(aResult->angularVelocity[1])) ||
|
||||||
|
!ReadParam(aMsg, aIter, &(aResult->angularVelocity[2])) ||
|
||||||
|
!ReadParam(aMsg, aIter, &(aResult->angularAcceleration[0])) ||
|
||||||
|
!ReadParam(aMsg, aIter, &(aResult->angularAcceleration[1])) ||
|
||||||
|
!ReadParam(aMsg, aIter, &(aResult->angularAcceleration[2])) ||
|
||||||
|
!ReadParam(aMsg, aIter, &(aResult->linearVelocity[0])) ||
|
||||||
|
!ReadParam(aMsg, aIter, &(aResult->linearVelocity[1])) ||
|
||||||
|
!ReadParam(aMsg, aIter, &(aResult->linearVelocity[2])) ||
|
||||||
|
!ReadParam(aMsg, aIter, &(aResult->linearAcceleration[0])) ||
|
||||||
|
!ReadParam(aMsg, aIter, &(aResult->linearAcceleration[1])) ||
|
||||||
|
!ReadParam(aMsg, aIter, &(aResult->linearAcceleration[2]))) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
} // namespace IPC
|
} // namespace IPC
|
||||||
|
|
||||||
#endif // mozilla_dom_gamepad_GamepadMessageUtils_h
|
#endif // mozilla_dom_gamepad_GamepadMessageUtils_h
|
|
@ -11,6 +11,7 @@ IPDL_SOURCES += [
|
||||||
]
|
]
|
||||||
|
|
||||||
EXPORTS.mozilla.dom += [
|
EXPORTS.mozilla.dom += [
|
||||||
|
'GamepadPoseState.h',
|
||||||
'ipc/GamepadMessageUtils.h',
|
'ipc/GamepadMessageUtils.h',
|
||||||
'ipc/GamepadServiceType.h'
|
'ipc/GamepadServiceType.h'
|
||||||
]
|
]
|
||||||
|
@ -22,6 +23,7 @@ if CONFIG['MOZ_GAMEPAD']:
|
||||||
'GamepadManager.h',
|
'GamepadManager.h',
|
||||||
'GamepadMonitoring.h',
|
'GamepadMonitoring.h',
|
||||||
'GamepadPlatformService.h',
|
'GamepadPlatformService.h',
|
||||||
|
'GamepadPose.h',
|
||||||
'GamepadServiceTest.h',
|
'GamepadServiceTest.h',
|
||||||
'ipc/GamepadEventChannelChild.h',
|
'ipc/GamepadEventChannelChild.h',
|
||||||
'ipc/GamepadEventChannelParent.h',
|
'ipc/GamepadEventChannelParent.h',
|
||||||
|
@ -35,6 +37,7 @@ if CONFIG['MOZ_GAMEPAD']:
|
||||||
'GamepadManager.cpp',
|
'GamepadManager.cpp',
|
||||||
'GamepadMonitoring.cpp',
|
'GamepadMonitoring.cpp',
|
||||||
'GamepadPlatformService.cpp',
|
'GamepadPlatformService.cpp',
|
||||||
|
'GamepadPose.cpp',
|
||||||
'GamepadServiceTest.cpp',
|
'GamepadServiceTest.cpp',
|
||||||
'ipc/GamepadEventChannelChild.cpp',
|
'ipc/GamepadEventChannelChild.cpp',
|
||||||
'ipc/GamepadEventChannelParent.cpp',
|
'ipc/GamepadEventChannelParent.cpp',
|
||||||
|
|
|
@ -404,6 +404,8 @@ var interfaceNamesInGlobalScope =
|
||||||
"GamepadButton",
|
"GamepadButton",
|
||||||
// IMPORTANT: Do not change this list without review from a DOM peer!
|
// IMPORTANT: Do not change this list without review from a DOM peer!
|
||||||
"GamepadEvent",
|
"GamepadEvent",
|
||||||
|
// IMPORTANT: Do not change this list without review from a DOM peer!
|
||||||
|
{name: "GamepadPose", release: false},
|
||||||
// IMPORTANT: Do not change this list without review from a DOM peer!
|
// IMPORTANT: Do not change this list without review from a DOM peer!
|
||||||
"HashChangeEvent",
|
"HashChangeEvent",
|
||||||
// IMPORTANT: Do not change this list without review from a DOM peer!
|
// IMPORTANT: Do not change this list without review from a DOM peer!
|
||||||
|
@ -774,6 +776,8 @@ var interfaceNamesInGlobalScope =
|
||||||
"PopupBlockedEvent",
|
"PopupBlockedEvent",
|
||||||
// IMPORTANT: Do not change this list without review from a DOM peer!
|
// IMPORTANT: Do not change this list without review from a DOM peer!
|
||||||
{name: "PopupBoxObject", xbl: true},
|
{name: "PopupBoxObject", xbl: true},
|
||||||
|
// IMPORTANT: Do not change this list without review from a DOM peer!
|
||||||
|
{name: "Pose", release: false},
|
||||||
// IMPORTANT: Do not change this list without review from a DOM peer!
|
// IMPORTANT: Do not change this list without review from a DOM peer!
|
||||||
{name: "PresentationDeviceInfoManager",
|
{name: "PresentationDeviceInfoManager",
|
||||||
disabled: true},
|
disabled: true},
|
||||||
|
|
|
@ -256,59 +256,16 @@ VRDisplayCapabilities::WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenPr
|
||||||
return VRDisplayCapabilitiesBinding::Wrap(aCx, this, aGivenProto);
|
return VRDisplayCapabilitiesBinding::Wrap(aCx, this, aGivenProto);
|
||||||
}
|
}
|
||||||
|
|
||||||
NS_IMPL_CYCLE_COLLECTION_CLASS(VRPose)
|
|
||||||
|
|
||||||
NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(VRPose)
|
|
||||||
NS_IMPL_CYCLE_COLLECTION_UNLINK(mParent)
|
|
||||||
NS_IMPL_CYCLE_COLLECTION_UNLINK_PRESERVED_WRAPPER
|
|
||||||
tmp->mPosition = nullptr;
|
|
||||||
tmp->mLinearVelocity = nullptr;
|
|
||||||
tmp->mLinearAcceleration = nullptr;
|
|
||||||
tmp->mOrientation = nullptr;
|
|
||||||
tmp->mAngularVelocity = nullptr;
|
|
||||||
tmp->mAngularAcceleration = nullptr;
|
|
||||||
NS_IMPL_CYCLE_COLLECTION_UNLINK_END
|
|
||||||
|
|
||||||
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(VRPose)
|
|
||||||
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mParent)
|
|
||||||
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_SCRIPT_OBJECTS
|
|
||||||
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
|
|
||||||
|
|
||||||
NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN(VRPose)
|
|
||||||
NS_IMPL_CYCLE_COLLECTION_TRACE_PRESERVED_WRAPPER
|
|
||||||
NS_IMPL_CYCLE_COLLECTION_TRACE_JS_MEMBER_CALLBACK(mPosition)
|
|
||||||
NS_IMPL_CYCLE_COLLECTION_TRACE_JS_MEMBER_CALLBACK(mLinearVelocity)
|
|
||||||
NS_IMPL_CYCLE_COLLECTION_TRACE_JS_MEMBER_CALLBACK(mLinearAcceleration)
|
|
||||||
NS_IMPL_CYCLE_COLLECTION_TRACE_JS_MEMBER_CALLBACK(mOrientation)
|
|
||||||
NS_IMPL_CYCLE_COLLECTION_TRACE_JS_MEMBER_CALLBACK(mAngularVelocity)
|
|
||||||
NS_IMPL_CYCLE_COLLECTION_TRACE_JS_MEMBER_CALLBACK(mAngularAcceleration)
|
|
||||||
NS_IMPL_CYCLE_COLLECTION_TRACE_END
|
|
||||||
|
|
||||||
NS_IMPL_CYCLE_COLLECTION_ROOT_NATIVE(VRPose, AddRef)
|
|
||||||
NS_IMPL_CYCLE_COLLECTION_UNROOT_NATIVE(VRPose, Release)
|
|
||||||
|
|
||||||
VRPose::VRPose(nsISupports* aParent, const gfx::VRHMDSensorState& aState)
|
VRPose::VRPose(nsISupports* aParent, const gfx::VRHMDSensorState& aState)
|
||||||
: mParent(aParent)
|
: Pose(aParent)
|
||||||
, mVRState(aState)
|
, mVRState(aState)
|
||||||
, mPosition(nullptr)
|
|
||||||
, mLinearVelocity(nullptr)
|
|
||||||
, mLinearAcceleration(nullptr)
|
|
||||||
, mOrientation(nullptr)
|
|
||||||
, mAngularVelocity(nullptr)
|
|
||||||
, mAngularAcceleration(nullptr)
|
|
||||||
{
|
{
|
||||||
mFrameId = aState.inputFrameID;
|
mFrameId = aState.inputFrameID;
|
||||||
mozilla::HoldJSObjects(this);
|
mozilla::HoldJSObjects(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
VRPose::VRPose(nsISupports* aParent)
|
VRPose::VRPose(nsISupports* aParent)
|
||||||
: mParent(aParent)
|
: Pose(aParent)
|
||||||
, mPosition(nullptr)
|
|
||||||
, mLinearVelocity(nullptr)
|
|
||||||
, mLinearAcceleration(nullptr)
|
|
||||||
, mOrientation(nullptr)
|
|
||||||
, mAngularVelocity(nullptr)
|
|
||||||
, mAngularAcceleration(nullptr)
|
|
||||||
{
|
{
|
||||||
mFrameId = 0;
|
mFrameId = 0;
|
||||||
mVRState.Clear();
|
mVRState.Clear();
|
||||||
|
@ -325,15 +282,9 @@ VRPose::GetPosition(JSContext* aCx,
|
||||||
JS::MutableHandle<JSObject*> aRetval,
|
JS::MutableHandle<JSObject*> aRetval,
|
||||||
ErrorResult& aRv)
|
ErrorResult& aRv)
|
||||||
{
|
{
|
||||||
if (!mPosition && mVRState.flags & gfx::VRDisplayCapabilityFlags::Cap_Position) {
|
SetFloat32Array(aCx, aRetval, mPosition, mVRState.position, 3,
|
||||||
// Lazily create the Float32Array
|
!mPosition && bool(mVRState.flags & gfx::VRDisplayCapabilityFlags::Cap_Position),
|
||||||
mPosition = dom::Float32Array::Create(aCx, this, 3, mVRState.position);
|
aRv);
|
||||||
if (!mPosition) {
|
|
||||||
aRv.NoteJSContextException(aCx);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
aRetval.set(mPosition);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
|
@ -341,15 +292,9 @@ VRPose::GetLinearVelocity(JSContext* aCx,
|
||||||
JS::MutableHandle<JSObject*> aRetval,
|
JS::MutableHandle<JSObject*> aRetval,
|
||||||
ErrorResult& aRv)
|
ErrorResult& aRv)
|
||||||
{
|
{
|
||||||
if (!mLinearVelocity && mVRState.flags & gfx::VRDisplayCapabilityFlags::Cap_Position) {
|
SetFloat32Array(aCx, aRetval, mLinearVelocity, mVRState.linearVelocity, 3,
|
||||||
// Lazily create the Float32Array
|
!mLinearVelocity && bool(mVRState.flags & gfx::VRDisplayCapabilityFlags::Cap_Position),
|
||||||
mLinearVelocity = dom::Float32Array::Create(aCx, this, 3, mVRState.linearVelocity);
|
aRv);
|
||||||
if (!mLinearVelocity) {
|
|
||||||
aRv.NoteJSContextException(aCx);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
aRetval.set(mLinearVelocity);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
|
@ -357,15 +302,10 @@ VRPose::GetLinearAcceleration(JSContext* aCx,
|
||||||
JS::MutableHandle<JSObject*> aRetval,
|
JS::MutableHandle<JSObject*> aRetval,
|
||||||
ErrorResult& aRv)
|
ErrorResult& aRv)
|
||||||
{
|
{
|
||||||
if (!mLinearAcceleration && mVRState.flags & gfx::VRDisplayCapabilityFlags::Cap_LinearAcceleration) {
|
SetFloat32Array(aCx, aRetval, mLinearAcceleration, mVRState.linearAcceleration, 3,
|
||||||
// Lazily create the Float32Array
|
!mLinearAcceleration && bool(mVRState.flags & gfx::VRDisplayCapabilityFlags::Cap_LinearAcceleration),
|
||||||
mLinearAcceleration = dom::Float32Array::Create(aCx, this, 3, mVRState.linearAcceleration);
|
aRv);
|
||||||
if (!mLinearAcceleration) {
|
|
||||||
aRv.NoteJSContextException(aCx);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
aRetval.set(mLinearAcceleration);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
|
@ -373,15 +313,9 @@ VRPose::GetOrientation(JSContext* aCx,
|
||||||
JS::MutableHandle<JSObject*> aRetval,
|
JS::MutableHandle<JSObject*> aRetval,
|
||||||
ErrorResult& aRv)
|
ErrorResult& aRv)
|
||||||
{
|
{
|
||||||
if (!mOrientation && mVRState.flags & gfx::VRDisplayCapabilityFlags::Cap_Orientation) {
|
SetFloat32Array(aCx, aRetval, mOrientation, mVRState.orientation, 4,
|
||||||
// Lazily create the Float32Array
|
!mOrientation && bool(mVRState.flags & gfx::VRDisplayCapabilityFlags::Cap_Orientation),
|
||||||
mOrientation = dom::Float32Array::Create(aCx, this, 4, mVRState.orientation);
|
aRv);
|
||||||
if (!mOrientation) {
|
|
||||||
aRv.NoteJSContextException(aCx);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
aRetval.set(mOrientation);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
|
@ -389,15 +323,9 @@ VRPose::GetAngularVelocity(JSContext* aCx,
|
||||||
JS::MutableHandle<JSObject*> aRetval,
|
JS::MutableHandle<JSObject*> aRetval,
|
||||||
ErrorResult& aRv)
|
ErrorResult& aRv)
|
||||||
{
|
{
|
||||||
if (!mAngularVelocity && mVRState.flags & gfx::VRDisplayCapabilityFlags::Cap_Orientation) {
|
SetFloat32Array(aCx, aRetval, mAngularVelocity, mVRState.angularVelocity, 3,
|
||||||
// Lazily create the Float32Array
|
!mAngularVelocity && bool(mVRState.flags & gfx::VRDisplayCapabilityFlags::Cap_Orientation),
|
||||||
mAngularVelocity = dom::Float32Array::Create(aCx, this, 3, mVRState.angularVelocity);
|
aRv);
|
||||||
if (!mAngularVelocity) {
|
|
||||||
aRv.NoteJSContextException(aCx);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
aRetval.set(mAngularVelocity);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
|
@ -405,15 +333,9 @@ VRPose::GetAngularAcceleration(JSContext* aCx,
|
||||||
JS::MutableHandle<JSObject*> aRetval,
|
JS::MutableHandle<JSObject*> aRetval,
|
||||||
ErrorResult& aRv)
|
ErrorResult& aRv)
|
||||||
{
|
{
|
||||||
if (!mAngularAcceleration && mVRState.flags & gfx::VRDisplayCapabilityFlags::Cap_AngularAcceleration) {
|
SetFloat32Array(aCx, aRetval, mAngularAcceleration, mVRState.angularAcceleration, 3,
|
||||||
// Lazily create the Float32Array
|
!mAngularAcceleration && bool(mVRState.flags & gfx::VRDisplayCapabilityFlags::Cap_AngularAcceleration),
|
||||||
mAngularAcceleration = dom::Float32Array::Create(aCx, this, 3, mVRState.angularAcceleration);
|
aRv);
|
||||||
if (!mAngularAcceleration) {
|
|
||||||
aRv.NoteJSContextException(aCx);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
aRetval.set(mAngularAcceleration);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
JSObject*
|
JSObject*
|
||||||
|
|
|
@ -15,11 +15,11 @@
|
||||||
#include "mozilla/DOMEventTargetHelper.h"
|
#include "mozilla/DOMEventTargetHelper.h"
|
||||||
#include "mozilla/dom/DOMPoint.h"
|
#include "mozilla/dom/DOMPoint.h"
|
||||||
#include "mozilla/dom/DOMRect.h"
|
#include "mozilla/dom/DOMRect.h"
|
||||||
|
#include "mozilla/dom/Pose.h"
|
||||||
|
|
||||||
#include "nsCOMPtr.h"
|
#include "nsCOMPtr.h"
|
||||||
#include "nsString.h"
|
#include "nsString.h"
|
||||||
#include "nsTArray.h"
|
#include "nsTArray.h"
|
||||||
#include "nsWrapperCache.h"
|
|
||||||
|
|
||||||
#include "gfxVR.h"
|
#include "gfxVR.h"
|
||||||
|
|
||||||
|
@ -95,54 +95,41 @@ protected:
|
||||||
gfx::VRDisplayCapabilityFlags mFlags;
|
gfx::VRDisplayCapabilityFlags mFlags;
|
||||||
};
|
};
|
||||||
|
|
||||||
class VRPose final : public nsWrapperCache
|
class VRPose final : public Pose
|
||||||
{
|
{
|
||||||
|
|
||||||
public:
|
public:
|
||||||
VRPose(nsISupports* aParent, const gfx::VRHMDSensorState& aState);
|
VRPose(nsISupports* aParent, const gfx::VRHMDSensorState& aState);
|
||||||
explicit VRPose(nsISupports* aParent);
|
explicit VRPose(nsISupports* aParent);
|
||||||
|
|
||||||
NS_INLINE_DECL_CYCLE_COLLECTING_NATIVE_REFCOUNTING(VRPose)
|
|
||||||
NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_NATIVE_CLASS(VRPose)
|
|
||||||
|
|
||||||
uint32_t FrameID() const { return mFrameId; }
|
uint32_t FrameID() const { return mFrameId; }
|
||||||
|
|
||||||
void GetPosition(JSContext* aCx,
|
virtual void GetPosition(JSContext* aCx,
|
||||||
JS::MutableHandle<JSObject*> aRetval,
|
JS::MutableHandle<JSObject*> aRetval,
|
||||||
ErrorResult& aRv);
|
ErrorResult& aRv) override;
|
||||||
void GetLinearVelocity(JSContext* aCx,
|
virtual void GetLinearVelocity(JSContext* aCx,
|
||||||
JS::MutableHandle<JSObject*> aRetval,
|
JS::MutableHandle<JSObject*> aRetval,
|
||||||
ErrorResult& aRv);
|
ErrorResult& aRv) override;
|
||||||
void GetLinearAcceleration(JSContext* aCx,
|
virtual void GetLinearAcceleration(JSContext* aCx,
|
||||||
JS::MutableHandle<JSObject*> aRetval,
|
JS::MutableHandle<JSObject*> aRetval,
|
||||||
ErrorResult& aRv);
|
ErrorResult& aRv) override;
|
||||||
void GetOrientation(JSContext* aCx,
|
virtual void GetOrientation(JSContext* aCx,
|
||||||
JS::MutableHandle<JSObject*> aRetval,
|
JS::MutableHandle<JSObject*> aRetval,
|
||||||
ErrorResult& aRv);
|
ErrorResult& aRv) override;
|
||||||
void GetAngularVelocity(JSContext* aCx,
|
virtual void GetAngularVelocity(JSContext* aCx,
|
||||||
JS::MutableHandle<JSObject*> aRetval,
|
JS::MutableHandle<JSObject*> aRetval,
|
||||||
ErrorResult& aRv);
|
ErrorResult& aRv) override;
|
||||||
void GetAngularAcceleration(JSContext* aCx,
|
virtual void GetAngularAcceleration(JSContext* aCx,
|
||||||
JS::MutableHandle<JSObject*> aRetval,
|
JS::MutableHandle<JSObject*> aRetval,
|
||||||
ErrorResult& aRv);
|
ErrorResult& aRv) override;
|
||||||
|
|
||||||
nsISupports* GetParentObject() const { return mParent; }
|
|
||||||
virtual JSObject* WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto) override;
|
virtual JSObject* WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto) override;
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
~VRPose();
|
~VRPose();
|
||||||
nsCOMPtr<nsISupports> mParent;
|
|
||||||
|
|
||||||
uint32_t mFrameId;
|
uint32_t mFrameId;
|
||||||
gfx::VRHMDSensorState mVRState;
|
gfx::VRHMDSensorState mVRState;
|
||||||
|
|
||||||
JS::Heap<JSObject*> mPosition;
|
|
||||||
JS::Heap<JSObject*> mLinearVelocity;
|
|
||||||
JS::Heap<JSObject*> mLinearAcceleration;
|
|
||||||
JS::Heap<JSObject*> mOrientation;
|
|
||||||
JS::Heap<JSObject*> mAngularVelocity;
|
|
||||||
JS::Heap<JSObject*> mAngularAcceleration;
|
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
struct VRFrameInfo
|
struct VRFrameInfo
|
||||||
|
|
|
@ -56,4 +56,10 @@ interface Gamepad {
|
||||||
* Timestamp from when the data of this device was last updated.
|
* Timestamp from when the data of this device was last updated.
|
||||||
*/
|
*/
|
||||||
readonly attribute DOMHighResTimeStamp timestamp;
|
readonly attribute DOMHighResTimeStamp timestamp;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The current pose of the device, a GamepadPose.
|
||||||
|
*/
|
||||||
|
[Pref="dom.gamepad.extensions.enabled"]
|
||||||
|
readonly attribute GamepadPose? pose;
|
||||||
};
|
};
|
||||||
|
|
|
@ -0,0 +1,13 @@
|
||||||
|
/* -*- 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/.
|
||||||
|
*/
|
||||||
|
|
||||||
|
[Pref="dom.gamepad.extensions.enabled",
|
||||||
|
HeaderFile="mozilla/dom/GamepadPose.h"]
|
||||||
|
interface GamepadPose : Pose
|
||||||
|
{
|
||||||
|
readonly attribute boolean hasOrientation;
|
||||||
|
readonly attribute boolean hasPosition;
|
||||||
|
};
|
|
@ -0,0 +1,23 @@
|
||||||
|
/* -*- 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/.
|
||||||
|
*/
|
||||||
|
|
||||||
|
interface Pose
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* position, linearVelocity, and linearAcceleration are 3-component vectors.
|
||||||
|
* position is relative to a sitting space. Transforming this point with
|
||||||
|
* VRStageParameters.sittingToStandingTransform converts this to standing space.
|
||||||
|
*/
|
||||||
|
[Constant, Throws] readonly attribute Float32Array? position;
|
||||||
|
[Constant, Throws] readonly attribute Float32Array? linearVelocity;
|
||||||
|
[Constant, Throws] readonly attribute Float32Array? linearAcceleration;
|
||||||
|
|
||||||
|
/* orientation is a 4-entry array representing the components of a quaternion. */
|
||||||
|
[Constant, Throws] readonly attribute Float32Array? orientation;
|
||||||
|
/* angularVelocity and angularAcceleration are the components of 3-dimensional vectors. */
|
||||||
|
[Constant, Throws] readonly attribute Float32Array? angularVelocity;
|
||||||
|
[Constant, Throws] readonly attribute Float32Array? angularAcceleration;
|
||||||
|
};
|
|
@ -116,21 +116,9 @@ interface VRStageParameters {
|
||||||
|
|
||||||
[Pref="dom.vr.enabled",
|
[Pref="dom.vr.enabled",
|
||||||
HeaderFile="mozilla/dom/VRDisplay.h"]
|
HeaderFile="mozilla/dom/VRDisplay.h"]
|
||||||
interface VRPose {
|
interface VRPose : Pose
|
||||||
/**
|
{
|
||||||
* position, linearVelocity, and linearAcceleration are 3-component vectors.
|
|
||||||
* position is relative to a sitting space. Transforming this point with
|
|
||||||
* VRStageParameters.sittingToStandingTransform converts this to standing space.
|
|
||||||
*/
|
|
||||||
[Constant, Throws] readonly attribute Float32Array? position;
|
|
||||||
[Constant, Throws] readonly attribute Float32Array? linearVelocity;
|
|
||||||
[Constant, Throws] readonly attribute Float32Array? linearAcceleration;
|
|
||||||
|
|
||||||
/* orientation is a 4-entry array representing the components of a quaternion. */
|
|
||||||
[Constant, Throws] readonly attribute Float32Array? orientation;
|
|
||||||
/* angularVelocity and angularAcceleration are the components of 3-dimensional vectors. */
|
|
||||||
[Constant, Throws] readonly attribute Float32Array? angularVelocity;
|
|
||||||
[Constant, Throws] readonly attribute Float32Array? angularAcceleration;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
[Constructor,
|
[Constructor,
|
||||||
|
|
|
@ -367,6 +367,7 @@ WEBIDL_FILES = [
|
||||||
'PluginArray.webidl',
|
'PluginArray.webidl',
|
||||||
'PointerEvent.webidl',
|
'PointerEvent.webidl',
|
||||||
'PopupBoxObject.webidl',
|
'PopupBoxObject.webidl',
|
||||||
|
'Pose.webidl',
|
||||||
'Position.webidl',
|
'Position.webidl',
|
||||||
'PositionError.webidl',
|
'PositionError.webidl',
|
||||||
'Presentation.webidl',
|
'Presentation.webidl',
|
||||||
|
@ -647,6 +648,7 @@ if CONFIG['MOZ_WEBSPEECH']:
|
||||||
if CONFIG['MOZ_GAMEPAD']:
|
if CONFIG['MOZ_GAMEPAD']:
|
||||||
WEBIDL_FILES += [
|
WEBIDL_FILES += [
|
||||||
'Gamepad.webidl',
|
'Gamepad.webidl',
|
||||||
|
'GamepadPose.webidl',
|
||||||
'GamepadServiceTest.webidl'
|
'GamepadServiceTest.webidl'
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
|
@ -1956,6 +1956,19 @@ HTMLEditRules::WillDeleteSelection(Selection* aSelection,
|
||||||
DeprecatedAbs(eo - so));
|
DeprecatedAbs(eo - so));
|
||||||
NS_ENSURE_SUCCESS(rv, rv);
|
NS_ENSURE_SUCCESS(rv, rv);
|
||||||
|
|
||||||
|
// XXX When Backspace key is pressed, Chromium removes following empty
|
||||||
|
// text nodes when removing the last character of the non-empty text
|
||||||
|
// node. However, Edge never removes empty text nodes even if
|
||||||
|
// selection is in the following empty text node(s). For now, we
|
||||||
|
// should keep our traditional behavior same as Edge for backward
|
||||||
|
// compatibility.
|
||||||
|
// XXX When Delete key is pressed, Edge removes all preceding empty
|
||||||
|
// text nodes when removing the first character of the non-empty
|
||||||
|
// text node. Chromium removes only selected empty text node and
|
||||||
|
// following empty text nodes and the first character of the
|
||||||
|
// non-empty text node. For now, we should keep our traditional
|
||||||
|
// behavior same as Chromium for backward compatibility.
|
||||||
|
|
||||||
DeleteNodeIfCollapsedText(nodeAsText);
|
DeleteNodeIfCollapsedText(nodeAsText);
|
||||||
|
|
||||||
rv = InsertBRIfNeeded(aSelection);
|
rv = InsertBRIfNeeded(aSelection);
|
||||||
|
|
|
@ -483,14 +483,12 @@ WSRunObject::PriorVisibleNode(nsINode* aNode,
|
||||||
for (; run; run = run->mLeft) {
|
for (; run; run = run->mLeft) {
|
||||||
if (run->mType == WSType::normalWS) {
|
if (run->mType == WSType::normalWS) {
|
||||||
WSPoint point = GetCharBefore(aNode, aOffset);
|
WSPoint point = GetCharBefore(aNode, aOffset);
|
||||||
if (point.mTextNode) {
|
// When it's a non-empty text node, return it.
|
||||||
|
if (point.mTextNode && point.mTextNode->Length()) {
|
||||||
*outVisNode = point.mTextNode;
|
*outVisNode = point.mTextNode;
|
||||||
*outVisOffset = point.mOffset + 1;
|
*outVisOffset = point.mOffset + 1;
|
||||||
if (nsCRT::IsAsciiSpace(point.mChar) || point.mChar == nbsp) {
|
if (nsCRT::IsAsciiSpace(point.mChar) || point.mChar == nbsp) {
|
||||||
*outType = WSType::normalWS;
|
*outType = WSType::normalWS;
|
||||||
} else if (!point.mChar) {
|
|
||||||
// MOOSE: not possible?
|
|
||||||
*outType = WSType::none;
|
|
||||||
} else {
|
} else {
|
||||||
*outType = WSType::text;
|
*outType = WSType::text;
|
||||||
}
|
}
|
||||||
|
@ -527,14 +525,12 @@ WSRunObject::NextVisibleNode(nsINode* aNode,
|
||||||
for (; run; run = run->mRight) {
|
for (; run; run = run->mRight) {
|
||||||
if (run->mType == WSType::normalWS) {
|
if (run->mType == WSType::normalWS) {
|
||||||
WSPoint point = GetCharAfter(aNode, aOffset);
|
WSPoint point = GetCharAfter(aNode, aOffset);
|
||||||
if (point.mTextNode) {
|
// When it's a non-empty text node, return it.
|
||||||
|
if (point.mTextNode && point.mTextNode->Length()) {
|
||||||
*outVisNode = point.mTextNode;
|
*outVisNode = point.mTextNode;
|
||||||
*outVisOffset = point.mOffset;
|
*outVisOffset = point.mOffset;
|
||||||
if (nsCRT::IsAsciiSpace(point.mChar) || point.mChar == nbsp) {
|
if (nsCRT::IsAsciiSpace(point.mChar) || point.mChar == nbsp) {
|
||||||
*outType = WSType::normalWS;
|
*outType = WSType::normalWS;
|
||||||
} else if (!point.mChar) {
|
|
||||||
// MOOSE: not possible?
|
|
||||||
*outType = WSType::none;
|
|
||||||
} else {
|
} else {
|
||||||
*outType = WSType::text;
|
*outType = WSType::text;
|
||||||
}
|
}
|
||||||
|
|
|
@ -210,6 +210,7 @@ skip-if = toolkit == 'android'
|
||||||
[test_bug1248185.html]
|
[test_bug1248185.html]
|
||||||
[test_bug1258085.html]
|
[test_bug1258085.html]
|
||||||
[test_bug1268736.html]
|
[test_bug1268736.html]
|
||||||
|
[test_bug1315065.html]
|
||||||
|
|
||||||
[test_CF_HTML_clipboard.html]
|
[test_CF_HTML_clipboard.html]
|
||||||
subsuite = clipboard
|
subsuite = clipboard
|
||||||
|
|
|
@ -0,0 +1,145 @@
|
||||||
|
<!DOCTYPE HTML>
|
||||||
|
<html>
|
||||||
|
<!--
|
||||||
|
https://bugzilla.mozilla.org/show_bug.cgi?id=1315065
|
||||||
|
-->
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<title>Test for Bug 1315065</title>
|
||||||
|
<script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
|
||||||
|
<script type="application/javascript" src="/tests/SimpleTest/EventUtils.js"></script>
|
||||||
|
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1315065">Mozilla Bug 1315065</a>
|
||||||
|
<div contenteditable><p>abc<br></p></div>
|
||||||
|
<script type="application/javascript">
|
||||||
|
/** Test for Bug 1315065 **/
|
||||||
|
SimpleTest.waitForExplicitFinish();
|
||||||
|
SimpleTest.waitForFocus(()=>{
|
||||||
|
var editor = document.getElementsByTagName("div")[0];
|
||||||
|
function initForBackspace(aSelectionCollapsedTo /* = 0 ~ 3 */) {
|
||||||
|
editor.innerHTML = "<p id='p'>abc<br></p>";
|
||||||
|
var p = document.getElementById("p");
|
||||||
|
// FYI: We cannot inserting empty text nodes as expected with
|
||||||
|
// Node.appendChild() nor Node.insertBefore(). Therefore, let's use
|
||||||
|
// Range.insertNode() like actual web apps.
|
||||||
|
var selection = window.getSelection();
|
||||||
|
selection.collapse(p, 1);
|
||||||
|
var range = selection.getRangeAt(0);
|
||||||
|
var emptyTextNode3 = document.createTextNode("");
|
||||||
|
range.insertNode(emptyTextNode3);
|
||||||
|
var emptyTextNode2 = document.createTextNode("");
|
||||||
|
range.insertNode(emptyTextNode2);
|
||||||
|
var emptyTextNode1 = document.createTextNode("");
|
||||||
|
range.insertNode(emptyTextNode1);
|
||||||
|
is(p.childNodes.length, 5, "Failed to initialize the editor");
|
||||||
|
is(p.childNodes.item(1), emptyTextNode1, "1st text node should be emptyTextNode1");
|
||||||
|
is(p.childNodes.item(2), emptyTextNode2, "2nd text node should be emptyTextNode2");
|
||||||
|
is(p.childNodes.item(3), emptyTextNode3, "3rd text node should be emptyTextNode3");
|
||||||
|
switch (aSelectionCollapsedTo) {
|
||||||
|
case 0:
|
||||||
|
selection.collapse(p.firstChild, 3); // next to 'c'
|
||||||
|
break;
|
||||||
|
case 1:
|
||||||
|
selection.collapse(emptyTextNode1, 0);
|
||||||
|
break;
|
||||||
|
case 2:
|
||||||
|
selection.collapse(emptyTextNode2, 0);
|
||||||
|
break;
|
||||||
|
case 3:
|
||||||
|
selection.collapse(emptyTextNode3, 0);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
ok(false, "aSelectionCollapsedTo is illegal value");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (var i = 0; i < 4; i++) {
|
||||||
|
const kDescription = i == 0 ? "Backspace from immediately after the last character" :
|
||||||
|
"Backspace from " + i + "th empty text node";
|
||||||
|
editor.focus();
|
||||||
|
initForBackspace(i);
|
||||||
|
synthesizeKey("KEY_Backspace", { code: "Backspace" });
|
||||||
|
var p = document.getElementById("p");
|
||||||
|
ok(p, kDescription + ": <p> element shouldn't be removed by Backspace key press");
|
||||||
|
is(p.tagName.toLowerCase(), "p", kDescription + ": <p> element shouldn't be removed by Backspace key press");
|
||||||
|
// When Backspace key is pressed even in empty text nodes, Gecko should not remove empty text nodes for now
|
||||||
|
// because we should keep our traditional behavior (same as Edge) for backward compatibility as far as possible.
|
||||||
|
// In this case, Chromium removes all empty text nodes, but Edge doesn't remove any empty text nodes.
|
||||||
|
is(p.childNodes.length, 5, kDescription + ": <p> should have 5 children after pressing Backspace key");
|
||||||
|
is(p.childNodes.item(0).textContent, "ab", kDescription + ": 'c' should be removed by pressing Backspace key");
|
||||||
|
is(p.childNodes.item(1).textContent, "", kDescription + ": 1st empty text node should not be removed by pressing Backspace key");
|
||||||
|
is(p.childNodes.item(2).textContent, "", kDescription + ": 2nd empty text node should not be removed by pressing Backspace key");
|
||||||
|
is(p.childNodes.item(3).textContent, "", kDescription + ": 3rd empty text node should not be removed by pressing Backspace key");
|
||||||
|
editor.blur();
|
||||||
|
}
|
||||||
|
|
||||||
|
function initForDelete(aSelectionCollapsedTo /* = 0 ~ 3 */) {
|
||||||
|
editor.innerHTML = "<p id='p'>abc<br></p>";
|
||||||
|
var p = document.getElementById("p");
|
||||||
|
// FYI: We cannot inserting empty text nodes as expected with
|
||||||
|
// Node.appendChild() nor Node.insertBefore(). Therefore, let's use
|
||||||
|
// Range.insertNode() like actual web apps.
|
||||||
|
var selection = window.getSelection();
|
||||||
|
selection.collapse(p, 0);
|
||||||
|
var range = selection.getRangeAt(0);
|
||||||
|
var emptyTextNode1 = document.createTextNode("");
|
||||||
|
range.insertNode(emptyTextNode1);
|
||||||
|
var emptyTextNode2 = document.createTextNode("");
|
||||||
|
range.insertNode(emptyTextNode2);
|
||||||
|
var emptyTextNode3 = document.createTextNode("");
|
||||||
|
range.insertNode(emptyTextNode3);
|
||||||
|
is(p.childNodes.length, 5, "Failed to initialize the editor");
|
||||||
|
is(p.childNodes.item(0), emptyTextNode3, "1st text node should be emptyTextNode3");
|
||||||
|
is(p.childNodes.item(1), emptyTextNode2, "2nd text node should be emptyTextNode2");
|
||||||
|
is(p.childNodes.item(2), emptyTextNode1, "3rd text node should be emptyTextNode1");
|
||||||
|
switch (aSelectionCollapsedTo) {
|
||||||
|
case 0:
|
||||||
|
selection.collapse(p.childNodes.item(3), 0); // next to 'a'
|
||||||
|
break;
|
||||||
|
case 1:
|
||||||
|
selection.collapse(emptyTextNode1, 0);
|
||||||
|
break;
|
||||||
|
case 2:
|
||||||
|
selection.collapse(emptyTextNode2, 0);
|
||||||
|
break;
|
||||||
|
case 3:
|
||||||
|
selection.collapse(emptyTextNode3, 0);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
ok(false, "aSelectionCollapsedTo is illegal value");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (var i = 0; i < 4; i++) {
|
||||||
|
const kDescription = i == 0 ? "Delete from immediately before the first character" :
|
||||||
|
"Delete from " + i + "th empty text node";
|
||||||
|
editor.focus();
|
||||||
|
initForDelete(i);
|
||||||
|
synthesizeKey("KEY_Delete", { code: "Delete" });
|
||||||
|
var p = document.getElementById("p");
|
||||||
|
ok(p, kDescription + ": <p> element shouldn't be removed by Delete key press");
|
||||||
|
is(p.tagName.toLowerCase(), "p", kDescription + ": <p> element shouldn't be removed by Delete key press");
|
||||||
|
if (i == 0) {
|
||||||
|
// If Delete key is pressed in non-empty text node, only the text node should be modified.
|
||||||
|
// This is same behavior as Chromium, but different from Edge. Edge removes all empty text nodes in this case.
|
||||||
|
is(p.childNodes.length, 5, kDescription + ": <p> should have only 2 children after pressing Delete key (empty text nodes should be removed");
|
||||||
|
is(p.childNodes.item(0).textContent, "", kDescription + ": 1st empty text node should not be removed by pressing Delete key");
|
||||||
|
is(p.childNodes.item(1).textContent, "", kDescription + ": 2nd empty text node should not be removed by pressing Delete key");
|
||||||
|
is(p.childNodes.item(2).textContent, "", kDescription + ": 3rd empty text node should not be removed by pressing Delete key");
|
||||||
|
is(p.childNodes.item(3).textContent, "bc", kDescription + ": 'a' should be removed by pressing Delete key");
|
||||||
|
} else {
|
||||||
|
// If Delete key is pressed in an empty text node, it and following empty text nodes should be removed and the non-empty text node should be modified.
|
||||||
|
// This is same behavior as Chromium, but different from Edge. Edge removes all empty text nodes in this case.
|
||||||
|
var expectedEmptyTextNodes = 3 - i;
|
||||||
|
is(p.childNodes.length, expectedEmptyTextNodes + 2, kDescription + ": <p> should have only " + i + " children after pressing Delete key (" + i + " empty text nodes should be removed");
|
||||||
|
is(p.childNodes.item(expectedEmptyTextNodes).textContent, "bc", kDescription + ": empty text nodes and 'a' should be removed by pressing Delete key");
|
||||||
|
}
|
||||||
|
editor.blur();
|
||||||
|
}
|
||||||
|
SimpleTest.finish();
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
|
@ -186,3 +186,16 @@ VRControllerHost::GetButtonPressed()
|
||||||
{
|
{
|
||||||
return mButtonPressed;
|
return mButtonPressed;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
VRControllerHost::SetPose(const dom::GamepadPoseState& aPose)
|
||||||
|
{
|
||||||
|
mPose = aPose;
|
||||||
|
}
|
||||||
|
|
||||||
|
const dom::GamepadPoseState&
|
||||||
|
VRControllerHost::GetPose()
|
||||||
|
{
|
||||||
|
return mPose;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
|
@ -16,6 +16,7 @@
|
||||||
#include "mozilla/EnumeratedArray.h"
|
#include "mozilla/EnumeratedArray.h"
|
||||||
#include "mozilla/TimeStamp.h"
|
#include "mozilla/TimeStamp.h"
|
||||||
#include "mozilla/TypedEnumBits.h"
|
#include "mozilla/TypedEnumBits.h"
|
||||||
|
#include "mozilla/dom/GamepadPoseState.h"
|
||||||
|
|
||||||
namespace mozilla {
|
namespace mozilla {
|
||||||
namespace layers {
|
namespace layers {
|
||||||
|
@ -92,6 +93,8 @@ public:
|
||||||
uint32_t GetIndex();
|
uint32_t GetIndex();
|
||||||
void SetButtonPressed(uint64_t aBit);
|
void SetButtonPressed(uint64_t aBit);
|
||||||
uint64_t GetButtonPressed();
|
uint64_t GetButtonPressed();
|
||||||
|
void SetPose(const dom::GamepadPoseState& aPose);
|
||||||
|
const dom::GamepadPoseState& GetPose();
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
explicit VRControllerHost(VRDeviceType aType);
|
explicit VRControllerHost(VRDeviceType aType);
|
||||||
|
@ -102,6 +105,7 @@ protected:
|
||||||
uint32_t mIndex;
|
uint32_t mIndex;
|
||||||
// The current button pressed bit of button mask.
|
// The current button pressed bit of button mask.
|
||||||
uint64_t mButtonPressed;
|
uint64_t mButtonPressed;
|
||||||
|
dom::GamepadPoseState mPose;
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace gfx
|
} // namespace gfx
|
||||||
|
|
|
@ -94,6 +94,10 @@ VRManager::VRManager()
|
||||||
mManagers.AppendElement(mgr);
|
mManagers.AppendElement(mgr);
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
// Enable gamepad extensions while VR is enabled.
|
||||||
|
if (gfxPrefs::VREnabled()) {
|
||||||
|
Preferences::SetBool("dom.gamepad.extensions.enabled", true);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
VRManager::~VRManager()
|
VRManager::~VRManager()
|
||||||
|
|
|
@ -113,3 +113,15 @@ VRControllerManager::NewAxisMove(uint32_t aIndex, uint32_t aAxis,
|
||||||
MOZ_ASSERT(vm);
|
MOZ_ASSERT(vm);
|
||||||
vm->NotifyGamepadChange<dom::GamepadAxisInformation>(a);
|
vm->NotifyGamepadChange<dom::GamepadAxisInformation>(a);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
VRControllerManager::NewPoseState(uint32_t aIndex,
|
||||||
|
const dom::GamepadPoseState& aPose)
|
||||||
|
{
|
||||||
|
dom::GamepadPoseInformation a(aIndex, dom::GamepadServiceType::VR,
|
||||||
|
aPose);
|
||||||
|
|
||||||
|
VRManager* vm = VRManager::Get();
|
||||||
|
MOZ_ASSERT(vm);
|
||||||
|
vm->NotifyGamepadChange<dom::GamepadPoseInformation>(a);
|
||||||
|
}
|
||||||
|
|
|
@ -20,6 +20,10 @@ namespace mozilla {
|
||||||
namespace layers {
|
namespace layers {
|
||||||
class PTextureParent;
|
class PTextureParent;
|
||||||
}
|
}
|
||||||
|
namespace dom {
|
||||||
|
enum class GamepadMappingType : uint32_t;
|
||||||
|
struct GamepadPoseState;
|
||||||
|
}
|
||||||
namespace gfx {
|
namespace gfx {
|
||||||
class VRLayerParent;
|
class VRLayerParent;
|
||||||
class VRDisplayHost;
|
class VRDisplayHost;
|
||||||
|
@ -252,6 +256,7 @@ public:
|
||||||
virtual void ScanForDevices() = 0;
|
virtual void ScanForDevices() = 0;
|
||||||
void NewButtonEvent(uint32_t aIndex, uint32_t aButton, bool aPressed);
|
void NewButtonEvent(uint32_t aIndex, uint32_t aButton, bool aPressed);
|
||||||
void NewAxisMove(uint32_t aIndex, uint32_t aAxis, double aValue);
|
void NewAxisMove(uint32_t aIndex, uint32_t aAxis, double aValue);
|
||||||
|
void NewPoseState(uint32_t aIndex, const dom::GamepadPoseState& aPose);
|
||||||
void AddGamepad(const char* aID, uint32_t aMapping,
|
void AddGamepad(const char* aID, uint32_t aMapping,
|
||||||
uint32_t aNumButtons, uint32_t aNumAxes);
|
uint32_t aNumButtons, uint32_t aNumAxes);
|
||||||
void RemoveGamepad(uint32_t aIndex);
|
void RemoveGamepad(uint32_t aIndex);
|
||||||
|
@ -269,6 +274,9 @@ private:
|
||||||
uint64_t aButtonPressed) = 0;
|
uint64_t aButtonPressed) = 0;
|
||||||
virtual void HandleAxisMove(uint32_t aControllerIdx, uint32_t aAxis,
|
virtual void HandleAxisMove(uint32_t aControllerIdx, uint32_t aAxis,
|
||||||
float aValue) = 0;
|
float aValue) = 0;
|
||||||
|
virtual void HandlePoseTracking(uint32_t aControllerIdx,
|
||||||
|
const dom::GamepadPoseState& aPose,
|
||||||
|
VRControllerHost* aController) = 0;
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace gfx
|
} // namespace gfx
|
||||||
|
|
|
@ -490,7 +490,7 @@ VRControllerOpenVR::VRControllerOpenVR()
|
||||||
MOZ_COUNT_CTOR_INHERITED(VRControllerOpenVR, VRControllerHost);
|
MOZ_COUNT_CTOR_INHERITED(VRControllerOpenVR, VRControllerHost);
|
||||||
mControllerInfo.mControllerName.AssignLiteral("OpenVR HMD");
|
mControllerInfo.mControllerName.AssignLiteral("OpenVR HMD");
|
||||||
#ifdef MOZ_GAMEPAD
|
#ifdef MOZ_GAMEPAD
|
||||||
mControllerInfo.mMappingType = static_cast<uint32_t>(dom::GamepadMappingType::_empty);
|
mControllerInfo.mMappingType = static_cast<uint32_t>(GamepadMappingType::_empty);
|
||||||
#else
|
#else
|
||||||
mControllerInfo.mMappingType = 0;
|
mControllerInfo.mMappingType = 0;
|
||||||
#endif
|
#endif
|
||||||
|
@ -583,6 +583,9 @@ VRControllerManagerOpenVR::HandleInput()
|
||||||
|
|
||||||
MOZ_ASSERT(mVRSystem);
|
MOZ_ASSERT(mVRSystem);
|
||||||
|
|
||||||
|
vr::TrackedDevicePose_t poses[vr::k_unMaxTrackedDeviceCount];
|
||||||
|
mVRSystem->GetDeviceToAbsoluteTrackingPose(vr::TrackingUniverseSeated, 0.0f,
|
||||||
|
poses, vr::k_unMaxTrackedDeviceCount);
|
||||||
// Process OpenVR controller state
|
// Process OpenVR controller state
|
||||||
for (uint32_t i = 0; i < mOpenVRController.Length(); ++i) {
|
for (uint32_t i = 0; i < mOpenVRController.Length(); ++i) {
|
||||||
controller = mOpenVRController[i];
|
controller = mOpenVRController[i];
|
||||||
|
@ -605,6 +608,44 @@ VRControllerManagerOpenVR::HandleInput()
|
||||||
HandleAxisMove(controller->GetIndex(), axis,
|
HandleAxisMove(controller->GetIndex(), axis,
|
||||||
state.rAxis[gOpenVRAxes[axis]].x);
|
state.rAxis[gOpenVRAxes[axis]].x);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Start to process pose
|
||||||
|
const ::vr::TrackedDevicePose_t& pose = poses[controller->GetTrackedIndex()];
|
||||||
|
|
||||||
|
if (pose.bDeviceIsConnected && pose.bPoseIsValid &&
|
||||||
|
pose.eTrackingResult == vr::TrackingResult_Running_OK) {
|
||||||
|
gfx::Matrix4x4 m;
|
||||||
|
|
||||||
|
// NOTE! mDeviceToAbsoluteTracking is a 3x4 matrix, not 4x4. But
|
||||||
|
// because of its arrangement, we can copy the 12 elements in and
|
||||||
|
// then transpose them to the right place. We do this so we can
|
||||||
|
// pull out a Quaternion.
|
||||||
|
memcpy(&m.components, &pose.mDeviceToAbsoluteTracking, sizeof(float) * 12);
|
||||||
|
m.Transpose();
|
||||||
|
|
||||||
|
gfx::Quaternion rot;
|
||||||
|
rot.SetFromRotationMatrix(m);
|
||||||
|
rot.Invert();
|
||||||
|
|
||||||
|
GamepadPoseState poseState;
|
||||||
|
poseState.flags |= GamepadCapabilityFlags::Cap_Orientation;
|
||||||
|
poseState.orientation[0] = rot.x;
|
||||||
|
poseState.orientation[1] = rot.y;
|
||||||
|
poseState.orientation[2] = rot.z;
|
||||||
|
poseState.orientation[3] = rot.w;
|
||||||
|
poseState.angularVelocity[0] = pose.vAngularVelocity.v[0];
|
||||||
|
poseState.angularVelocity[1] = pose.vAngularVelocity.v[1];
|
||||||
|
poseState.angularVelocity[2] = pose.vAngularVelocity.v[2];
|
||||||
|
|
||||||
|
poseState.flags |= GamepadCapabilityFlags::Cap_Position;
|
||||||
|
poseState.position[0] = m._41;
|
||||||
|
poseState.position[1] = m._42;
|
||||||
|
poseState.position[2] = m._43;
|
||||||
|
poseState.linearVelocity[0] = pose.vVelocity.v[0];
|
||||||
|
poseState.linearVelocity[1] = pose.vVelocity.v[1];
|
||||||
|
poseState.linearVelocity[2] = pose.vVelocity.v[2];
|
||||||
|
HandlePoseTracking(controller->GetIndex(), poseState, controller);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -644,6 +685,17 @@ VRControllerManagerOpenVR::HandleAxisMove(uint32_t aControllerIdx, uint32_t aAxi
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
VRControllerManagerOpenVR::HandlePoseTracking(uint32_t aControllerIdx,
|
||||||
|
const GamepadPoseState& aPose,
|
||||||
|
VRControllerHost* aController)
|
||||||
|
{
|
||||||
|
if (aPose != aController->GetPose()) {
|
||||||
|
aController->SetPose(aPose);
|
||||||
|
NewPoseState(aControllerIdx, aPose);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
VRControllerManagerOpenVR::GetControllers(nsTArray<RefPtr<VRControllerHost>>& aControllerResult)
|
VRControllerManagerOpenVR::GetControllers(nsTArray<RefPtr<VRControllerHost>>& aControllerResult)
|
||||||
{
|
{
|
||||||
|
|
|
@ -124,6 +124,9 @@ private:
|
||||||
uint64_t aButtonPressed) override;
|
uint64_t aButtonPressed) override;
|
||||||
virtual void HandleAxisMove(uint32_t aControllerIdx, uint32_t aAxis,
|
virtual void HandleAxisMove(uint32_t aControllerIdx, uint32_t aAxis,
|
||||||
float aValue) override;
|
float aValue) override;
|
||||||
|
virtual void HandlePoseTracking(uint32_t aControllerIdx,
|
||||||
|
const dom::GamepadPoseState& aPose,
|
||||||
|
VRControllerHost* aController) override;
|
||||||
|
|
||||||
bool mOpenVRInstalled;
|
bool mOpenVRInstalled;
|
||||||
nsTArray<RefPtr<impl::VRControllerOpenVR>> mOpenVRController;
|
nsTArray<RefPtr<impl::VRControllerOpenVR>> mOpenVRController;
|
||||||
|
|
Разница между файлами не показана из-за своего большого размера
Загрузить разницу
|
@ -50,7 +50,7 @@ https://bugzilla.mozilla.org/show_bug.cgi?id=435442
|
||||||
25% { margin-top: 100px }
|
25% { margin-top: 100px }
|
||||||
}
|
}
|
||||||
@keyframes kf4 {
|
@keyframes kf4 {
|
||||||
to, from { border-collapse: collapse; margin-top: 37px }
|
to, from { display: none; margin-top: 37px }
|
||||||
}
|
}
|
||||||
@keyframes kf_cascade1 {
|
@keyframes kf_cascade1 {
|
||||||
from { padding-top: 50px }
|
from { padding-top: 50px }
|
||||||
|
@ -566,23 +566,23 @@ done_div();
|
||||||
// we still override the value when two consecutive keyframes have
|
// we still override the value when two consecutive keyframes have
|
||||||
// the same value.
|
// the same value.
|
||||||
new_div("animation: kf4 ease 10s");
|
new_div("animation: kf4 ease 10s");
|
||||||
is(cs.borderCollapse, "separate",
|
is(cs.display, "block",
|
||||||
"non-animatable properties should be ignored (linear, 0s)");
|
"non-animatable properties should be ignored (linear, 0s)");
|
||||||
is(cs.marginTop, "37px",
|
is(cs.marginTop, "37px",
|
||||||
"animatable properties should still apply (linear, 0s)");
|
"animatable properties should still apply (linear, 0s)");
|
||||||
advance_clock(1000);
|
advance_clock(1000);
|
||||||
is(cs.borderCollapse, "separate",
|
is(cs.display, "block",
|
||||||
"non-animatable properties should be ignored (linear, 1s)");
|
"non-animatable properties should be ignored (linear, 1s)");
|
||||||
is(cs.marginTop, "37px",
|
is(cs.marginTop, "37px",
|
||||||
"animatable properties should still apply (linear, 1s)");
|
"animatable properties should still apply (linear, 1s)");
|
||||||
done_div();
|
done_div();
|
||||||
new_div("animation: kf4 step-start 10s");
|
new_div("animation: kf4 step-start 10s");
|
||||||
is(cs.borderCollapse, "separate",
|
is(cs.display, "block",
|
||||||
"non-animatable properties should be ignored (step-start, 0s)");
|
"non-animatable properties should be ignored (step-start, 0s)");
|
||||||
is(cs.marginTop, "37px",
|
is(cs.marginTop, "37px",
|
||||||
"animatable properties should still apply (step-start, 0s)");
|
"animatable properties should still apply (step-start, 0s)");
|
||||||
advance_clock(1000);
|
advance_clock(1000);
|
||||||
is(cs.borderCollapse, "separate",
|
is(cs.display, "block",
|
||||||
"non-animatable properties should be ignored (step-start, 1s)");
|
"non-animatable properties should be ignored (step-start, 1s)");
|
||||||
is(cs.marginTop, "37px",
|
is(cs.marginTop, "37px",
|
||||||
"animatable properties should still apply (step-start, 1s)");
|
"animatable properties should still apply (step-start, 1s)");
|
||||||
|
|
|
@ -61,7 +61,7 @@ https://bugzilla.mozilla.org/show_bug.cgi?id=964646
|
||||||
25% { transform: translate(100px) }
|
25% { transform: translate(100px) }
|
||||||
}
|
}
|
||||||
@keyframes kf4 {
|
@keyframes kf4 {
|
||||||
to, from { border-collapse: collapse; transform: translate(37px) }
|
to, from { display: none; transform: translate(37px) }
|
||||||
}
|
}
|
||||||
@keyframes kf_cascade1 {
|
@keyframes kf_cascade1 {
|
||||||
from { transform: translate(50px) }
|
from { transform: translate(50px) }
|
||||||
|
@ -634,12 +634,12 @@ addAsyncAnimTest(function *() {
|
||||||
new_div("animation: kf4 ease 10s");
|
new_div("animation: kf4 ease 10s");
|
||||||
yield waitForPaintsFlushed();
|
yield waitForPaintsFlushed();
|
||||||
var cs = window.getComputedStyle(gDiv);
|
var cs = window.getComputedStyle(gDiv);
|
||||||
is(cs.borderCollapse, "separate",
|
is(cs.display, "block",
|
||||||
"non-animatable properties should be ignored (linear, 0s)");
|
"non-animatable properties should be ignored (linear, 0s)");
|
||||||
omta_is("transform", { tx: 37 }, RunningOn.Compositor,
|
omta_is("transform", { tx: 37 }, RunningOn.Compositor,
|
||||||
"animatable properties should still apply (linear, 0s)");
|
"animatable properties should still apply (linear, 0s)");
|
||||||
advance_clock(1000);
|
advance_clock(1000);
|
||||||
is(cs.borderCollapse, "separate",
|
is(cs.display, "block",
|
||||||
"non-animatable properties should be ignored (linear, 1s)");
|
"non-animatable properties should be ignored (linear, 1s)");
|
||||||
omta_is("transform", { tx: 37 }, RunningOn.Compositor,
|
omta_is("transform", { tx: 37 }, RunningOn.Compositor,
|
||||||
"animatable properties should still apply (linear, 1s)");
|
"animatable properties should still apply (linear, 1s)");
|
||||||
|
@ -647,12 +647,12 @@ addAsyncAnimTest(function *() {
|
||||||
new_div("animation: kf4 step-start 10s");
|
new_div("animation: kf4 step-start 10s");
|
||||||
yield waitForPaintsFlushed();
|
yield waitForPaintsFlushed();
|
||||||
cs = window.getComputedStyle(gDiv);
|
cs = window.getComputedStyle(gDiv);
|
||||||
is(cs.borderCollapse, "separate",
|
is(cs.display, "block",
|
||||||
"non-animatable properties should be ignored (step-start, 0s)");
|
"non-animatable properties should be ignored (step-start, 0s)");
|
||||||
omta_is("transform", { tx: 37 }, RunningOn.Compositor,
|
omta_is("transform", { tx: 37 }, RunningOn.Compositor,
|
||||||
"animatable properties should still apply (step-start, 0s)");
|
"animatable properties should still apply (step-start, 0s)");
|
||||||
advance_clock(1000);
|
advance_clock(1000);
|
||||||
is(cs.borderCollapse, "separate",
|
is(cs.display, "block",
|
||||||
"non-animatable properties should be ignored (step-start, 1s)");
|
"non-animatable properties should be ignored (step-start, 1s)");
|
||||||
omta_is("transform", { tx: 37 }, RunningOn.Compositor,
|
omta_is("transform", { tx: 37 }, RunningOn.Compositor,
|
||||||
"animatable properties should still apply (step-start, 1s)");
|
"animatable properties should still apply (step-start, 1s)");
|
||||||
|
|
|
@ -203,6 +203,7 @@ pref("dom.gamepad.non_standard_events.enabled", false);
|
||||||
#else
|
#else
|
||||||
pref("dom.gamepad.non_standard_events.enabled", true);
|
pref("dom.gamepad.non_standard_events.enabled", true);
|
||||||
#endif
|
#endif
|
||||||
|
pref("dom.gamepad.extensions.enabled", false);
|
||||||
|
|
||||||
// Whether the KeyboardEvent.code is enabled
|
// Whether the KeyboardEvent.code is enabled
|
||||||
pref("dom.keyboardevent.code.enabled", true);
|
pref("dom.keyboardevent.code.enabled", true);
|
||||||
|
|
|
@ -1,3 +1,7 @@
|
||||||
|
prefs: [layout.css.contain.enabled:true,
|
||||||
|
layout.css.initial-letter.enabled:true,
|
||||||
|
layout.css.overflow-clip-box.enabled:true,
|
||||||
|
layout.css.shape-outside.enabled:true]
|
||||||
[type-per-property.html]
|
[type-per-property.html]
|
||||||
type: testharness
|
type: testharness
|
||||||
[flex-basis supports animating as combination units 'px' and '%']
|
[flex-basis supports animating as combination units 'px' and '%']
|
||||||
|
@ -31,4 +35,3 @@
|
||||||
[text-combine-upright uses discrete animation when animating between 'all' and 'digits' with keyframe easing]
|
[text-combine-upright uses discrete animation when animating between 'all' and 'digits' with keyframe easing]
|
||||||
expected: FAIL
|
expected: FAIL
|
||||||
bug: https://bugzilla.mozilla.org/show_bug.cgi?id=1258635
|
bug: https://bugzilla.mozilla.org/show_bug.cgi?id=1258635
|
||||||
|
|
||||||
|
|
|
@ -34,12 +34,144 @@ var gCSSProperties = {
|
||||||
discrete("flex-start", "flex-end")
|
discrete("flex-start", "flex-end")
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
"backface-visibility": {
|
||||||
|
// https://drafts.csswg.org/css-transforms/#propdef-backface-visibility
|
||||||
|
"tests": [
|
||||||
|
discrete("visible", "hidden")
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"background-attachment": {
|
||||||
|
// https://drafts.csswg.org/css-backgrounds-3/#background-attachment
|
||||||
|
"tests": [
|
||||||
|
discrete("fixed", "local")
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"background-blend-mode": {
|
||||||
|
// https://drafts.fxtf.org/compositing-1/#propdef-background-blend-mode
|
||||||
|
"tests": [
|
||||||
|
discrete("multiply", "screen")
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"background-clip": {
|
||||||
|
// https://drafts.csswg.org/css-backgrounds-3/#background-clip
|
||||||
|
"tests": [
|
||||||
|
discrete("padding-box", "content-box")
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"background-image": {
|
||||||
|
// https://drafts.csswg.org/css-backgrounds-3/#background-image
|
||||||
|
"tests": [
|
||||||
|
discrete("url(\"http://localhost/test-1\")", "url(\"http://localhost/test-2\")")
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"background-origin": {
|
||||||
|
// https://drafts.csswg.org/css-backgrounds-3/#background-origin
|
||||||
|
"tests": [
|
||||||
|
discrete("padding-box", "content-box")
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"background-repeat": {
|
||||||
|
// https://drafts.csswg.org/css-backgrounds-3/#background-repeat
|
||||||
|
"tests": [
|
||||||
|
discrete("space", "round")
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"border-bottom-style": {
|
||||||
|
// https://drafts.csswg.org/css-backgrounds-3/#border-bottom-style
|
||||||
|
"tests": [
|
||||||
|
discrete("dotted", "solid")
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"border-collapse": {
|
||||||
|
// https://drafts.csswg.org/css-tables/#propdef-border-collapse
|
||||||
|
"tests": [
|
||||||
|
discrete("collapse", "separate")
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"border-image-outset": {
|
||||||
|
// https://drafts.csswg.org/css-backgrounds-3/#border-image-outset
|
||||||
|
"tests": [
|
||||||
|
discrete("1 1 1 1", "5 5 5 5")
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"border-image-repeat": {
|
||||||
|
// https://drafts.csswg.org/css-backgrounds-3/#border-image-repeat
|
||||||
|
"tests": [
|
||||||
|
discrete("stretch stretch", "repeat repeat")
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"border-image-slice": {
|
||||||
|
// https://drafts.csswg.org/css-backgrounds-3/#border-image-slice
|
||||||
|
"tests": [
|
||||||
|
discrete("1 1 1 1", "5 5 5 5")
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"border-image-source": {
|
||||||
|
// https://drafts.csswg.org/css-backgrounds-3/#border-image-source
|
||||||
|
"tests": [
|
||||||
|
discrete("url(\"http://localhost/test-1\")", "url(\"http://localhost/test-2\")")
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"border-image-width": {
|
||||||
|
// https://drafts.csswg.org/css-backgrounds-3/#border-image-width
|
||||||
|
"tests": [
|
||||||
|
discrete("1 1 1 1", "5 5 5 5")
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"border-left-style": {
|
||||||
|
// https://drafts.csswg.org/css-backgrounds-3/#border-left-style
|
||||||
|
"tests": [
|
||||||
|
discrete("dotted", "solid")
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"border-right-style": {
|
||||||
|
// https://drafts.csswg.org/css-backgrounds-3/#border-right-style
|
||||||
|
"tests": [
|
||||||
|
discrete("dotted", "solid")
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"border-top-style": {
|
||||||
|
// https://drafts.csswg.org/css-backgrounds-3/#border-top-style
|
||||||
|
"tests": [
|
||||||
|
discrete("dotted", "solid")
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"box-decoration-break": {
|
||||||
|
// https://drafts.csswg.org/css-break/#propdef-box-decoration-break
|
||||||
|
"tests": [
|
||||||
|
discrete("slice", "clone")
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"box-sizing": {
|
||||||
|
// https://drafts.csswg.org/css-ui-4/#box-sizing
|
||||||
|
"tests": [
|
||||||
|
discrete("content-box", "border-box")
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"caption-side": {
|
||||||
|
// https://drafts.csswg.org/css-tables/#propdef-caption-side
|
||||||
|
"tests": [
|
||||||
|
discrete("top", "bottom")
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"clear": {
|
||||||
|
// https://drafts.csswg.org/css-page-floats/#propdef-clear
|
||||||
|
"tests": [
|
||||||
|
discrete("inline-start", "inline-end")
|
||||||
|
]
|
||||||
|
},
|
||||||
"clip-rule": {
|
"clip-rule": {
|
||||||
// https://drafts.fxtf.org/css-masking-1/#propdef-clip-rule
|
// https://drafts.fxtf.org/css-masking-1/#propdef-clip-rule
|
||||||
tests: [
|
tests: [
|
||||||
discrete("evenodd", "nonzero")
|
discrete("evenodd", "nonzero")
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
"color-adjust": {
|
||||||
|
// https://drafts.csswg.org/css-color-4/#color-adjust
|
||||||
|
tests: [
|
||||||
|
discrete("economy", "exact")
|
||||||
|
]
|
||||||
|
},
|
||||||
"color-interpolation": {
|
"color-interpolation": {
|
||||||
// https://svgwg.org/svg2-draft/painting.html#ColorInterpolationProperty
|
// https://svgwg.org/svg2-draft/painting.html#ColorInterpolationProperty
|
||||||
tests: [
|
tests: [
|
||||||
|
@ -52,12 +184,67 @@ var gCSSProperties = {
|
||||||
discrete("sRGB", "linearRGB")
|
discrete("sRGB", "linearRGB")
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
"column-fill": {
|
||||||
|
// https://drafts.csswg.org/css-multicol/#propdef-column-fill
|
||||||
|
tests: [
|
||||||
|
discrete("auto", "balance")
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"column-rule-style": {
|
||||||
|
// https://drafts.csswg.org/css-multicol/#propdef-column-rule-style
|
||||||
|
tests: [
|
||||||
|
discrete("none", "dotted")
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"contain": {
|
||||||
|
// https://drafts.csswg.org/css-containment/#propdef-contain
|
||||||
|
tests: [
|
||||||
|
discrete("strict", "none")
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"content": {
|
||||||
|
// https://drafts.csswg.org/css-content-3/#propdef-content
|
||||||
|
tests: [
|
||||||
|
discrete("\"a\"", "\"b\"")
|
||||||
|
],
|
||||||
|
tagName: "::before"
|
||||||
|
},
|
||||||
|
"counter-increment": {
|
||||||
|
// https://drafts.csswg.org/css-lists-3/#propdef-counter-increment
|
||||||
|
tests: [
|
||||||
|
discrete("ident-1 1", "ident-2 2")
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"counter-reset": {
|
||||||
|
// https://drafts.csswg.org/css-lists-3/#propdef-counter-reset
|
||||||
|
tests: [
|
||||||
|
discrete("ident-1 1", "ident-2 2")
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"cursor": {
|
||||||
|
// https://drafts.csswg.org/css2/ui.html#propdef-cursor
|
||||||
|
tests: [
|
||||||
|
discrete("pointer", "wait")
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"direction": {
|
||||||
|
// https://drafts.csswg.org/css-writing-modes-3/#propdef-direction
|
||||||
|
tests: [
|
||||||
|
discrete("ltr", "rtl")
|
||||||
|
]
|
||||||
|
},
|
||||||
"dominant-baseline": {
|
"dominant-baseline": {
|
||||||
// https://drafts.csswg.org/css-inline/#propdef-dominant-baseline
|
// https://drafts.csswg.org/css-inline/#propdef-dominant-baseline
|
||||||
tests: [
|
tests: [
|
||||||
discrete("ideographic", "alphabetic")
|
discrete("ideographic", "alphabetic")
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
"empty-cells": {
|
||||||
|
// https://drafts.csswg.org/css-tables/#propdef-empty-cells
|
||||||
|
tests: [
|
||||||
|
discrete("show", "hide")
|
||||||
|
]
|
||||||
|
},
|
||||||
"fill-rule": {
|
"fill-rule": {
|
||||||
// https://svgwg.org/svg2-draft/painting.html#FillRuleProperty
|
// https://svgwg.org/svg2-draft/painting.html#FillRuleProperty
|
||||||
tests: [
|
tests: [
|
||||||
|
@ -101,10 +288,172 @@ var gCSSProperties = {
|
||||||
discrete("italic", "oblique")
|
discrete("italic", "oblique")
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"image-rendering": {
|
"float": {
|
||||||
// https://drafts.csswg.org/css-images/#propdef-image-rendering
|
// https://drafts.csswg.org/css-page-floats/#propdef-float
|
||||||
tests: [
|
tests: [
|
||||||
discrete("optimizeQuality", "pixelated")
|
discrete("left", "right")
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"font-family": {
|
||||||
|
// https://drafts.csswg.org/css-fonts-3/#propdef-font-family
|
||||||
|
tests: [
|
||||||
|
discrete("helvetica", "verdana")
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"font-feature-settings": {
|
||||||
|
// https://drafts.csswg.org/css-fonts/#descdef-font-feature-settings
|
||||||
|
tests: [
|
||||||
|
discrete("\"liga\" 5", "normal")
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"font-kerning": {
|
||||||
|
// https://drafts.csswg.org/css-fonts-3/#propdef-font-kerning
|
||||||
|
tests: [
|
||||||
|
discrete("auto", "normal")
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"font-language-override": {
|
||||||
|
// https://drafts.csswg.org/css-fonts-3/#propdef-font-language-override
|
||||||
|
tests: [
|
||||||
|
discrete("\"eng\"", "normal")
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"font-style": {
|
||||||
|
// https://drafts.csswg.org/css-fonts-3/#propdef-font-style
|
||||||
|
tests: [
|
||||||
|
discrete("italic", "oblique")
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"font-synthesis": {
|
||||||
|
// https://drafts.csswg.org/css-fonts-3/#propdef-font-synthesis
|
||||||
|
tests: [
|
||||||
|
discrete("none", "weight style")
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"font-variant-alternates": {
|
||||||
|
// https://drafts.csswg.org/css-fonts-3/#propdef-font-variant-alternates
|
||||||
|
tests: [
|
||||||
|
discrete("swash(unknown)", "stylistic(unknown)")
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"font-variant-caps": {
|
||||||
|
// https://drafts.csswg.org/css-fonts-3/#propdef-font-variant-caps
|
||||||
|
tests: [
|
||||||
|
discrete("small-caps", "unicase")
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"font-variant-east-asian": {
|
||||||
|
// https://drafts.csswg.org/css-fonts-3/#propdef-font-variant-east-asian
|
||||||
|
tests: [
|
||||||
|
discrete("full-width", "proportional-width")
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"font-variant-ligatures": {
|
||||||
|
// https://drafts.csswg.org/css-fonts-3/#propdef-font-variant-ligatures
|
||||||
|
tests: [
|
||||||
|
discrete("common-ligatures", "no-common-ligatures")
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"font-variant-numeric": {
|
||||||
|
// https://drafts.csswg.org/css-fonts-3/#propdef-font-variant-numeric
|
||||||
|
tests: [
|
||||||
|
discrete("lining-nums", "oldstyle-nums")
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"font-variant-position": {
|
||||||
|
// https://drafts.csswg.org/css-fonts-3/#propdef-font-variant-position
|
||||||
|
tests: [
|
||||||
|
discrete("sub", "super")
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"grid-auto-columns": {
|
||||||
|
// https://drafts.csswg.org/css-grid/#propdef-grid-auto-columns
|
||||||
|
tests: [
|
||||||
|
discrete("1px", "5px")
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"grid-auto-flow": {
|
||||||
|
// https://drafts.csswg.org/css-grid/#propdef-grid-auto-flow
|
||||||
|
tests: [
|
||||||
|
discrete("row", "column")
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"grid-auto-rows": {
|
||||||
|
// https://drafts.csswg.org/css-grid/#propdef-grid-auto-rows
|
||||||
|
tests: [
|
||||||
|
discrete("1px", "5px")
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"grid-column-end": {
|
||||||
|
// https://drafts.csswg.org/css-grid/#propdef-grid-column-end
|
||||||
|
tests: [
|
||||||
|
discrete("1", "5")
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"grid-column-start": {
|
||||||
|
// https://drafts.csswg.org/css-grid/#propdef-grid-column-start
|
||||||
|
tests: [
|
||||||
|
discrete("1", "5")
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"grid-row-end": {
|
||||||
|
// https://drafts.csswg.org/css-grid/#propdef-grid-row-end
|
||||||
|
tests: [
|
||||||
|
discrete("1", "5")
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"grid-row-start": {
|
||||||
|
// https://drafts.csswg.org/css-grid/#propdef-grid-row-start
|
||||||
|
tests: [
|
||||||
|
discrete("1", "5")
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"grid-template-areas": {
|
||||||
|
// https://drafts.csswg.org/css-template/#grid-template-areas
|
||||||
|
tests: [
|
||||||
|
discrete("\". . a b\" \". .a b\"", "none")
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"grid-template-columns": {
|
||||||
|
// https://drafts.csswg.org/css-template/#grid-template-columns
|
||||||
|
tests: [
|
||||||
|
discrete("1px", "5px")
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"grid-template-rows": {
|
||||||
|
// https://drafts.csswg.org/css-template/#grid-template-rows
|
||||||
|
tests: [
|
||||||
|
discrete("1px", "5px")
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"hyphens": {
|
||||||
|
// https://drafts.csswg.org/css-text-3/#propdef-hyphens
|
||||||
|
tests: [
|
||||||
|
discrete("manual", "auto")
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"image-orientation": {
|
||||||
|
// https://drafts.csswg.org/css-images-3/#propdef-image-orientation
|
||||||
|
tests: [
|
||||||
|
discrete("0deg", "90deg")
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"ime-mode": {
|
||||||
|
// https://drafts.csswg.org/css-ui/#input-method-editor
|
||||||
|
tests: [
|
||||||
|
discrete("disabled", "auto")
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"initial-letter": {
|
||||||
|
// https://drafts.csswg.org/css-inline/#propdef-initial-letter
|
||||||
|
tests: [
|
||||||
|
discrete("1 2", "3 4")
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"isolation": {
|
||||||
|
// https://drafts.fxtf.org/compositing-1/#propdef-isolation
|
||||||
|
tests: [
|
||||||
|
discrete("auto", "isolate")
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"justify-content": {
|
"justify-content": {
|
||||||
|
@ -125,24 +474,186 @@ var gCSSProperties = {
|
||||||
discrete("baseline", "last baseline")
|
discrete("baseline", "last baseline")
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
"list-style-image": {
|
||||||
|
// https://drafts.csswg.org/css-lists-3/#propdef-list-style-image
|
||||||
|
tests: [
|
||||||
|
discrete("url(\"http://localhost/test-1\")", "url(\"http://localhost/test-2\")")
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"list-style-position": {
|
||||||
|
// https://drafts.csswg.org/css-lists-3/#propdef-list-style-position
|
||||||
|
tests: [
|
||||||
|
discrete("inside", "outside")
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"list-style-type": {
|
||||||
|
// https://drafts.csswg.org/css-lists-3/#propdef-list-style-type
|
||||||
|
tests: [
|
||||||
|
discrete("circle", "square")
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"marker-end": {
|
||||||
|
// https://svgwg.org/specs/markers/#MarkerEndProperty
|
||||||
|
tests: [
|
||||||
|
discrete("url(\"http://localhost/test-1\")", "url(\"http://localhost/test-2\")")
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"marker-mid": {
|
||||||
|
// https://svgwg.org/specs/markers/#MarkerMidProperty
|
||||||
|
tests: [
|
||||||
|
discrete("url(\"http://localhost/test-1\")", "url(\"http://localhost/test-2\")")
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"marker-start": {
|
||||||
|
// https://svgwg.org/specs/markers/#MarkerStartProperty
|
||||||
|
tests: [
|
||||||
|
discrete("url(\"http://localhost/test-1\")", "url(\"http://localhost/test-2\")")
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"mask": {
|
||||||
|
// https://drafts.fxtf.org/css-masking-1/#the-mask
|
||||||
|
tests: [
|
||||||
|
discrete("url(\"http://localhost/test-1\")", "url(\"http://localhost/test-2\")")
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"mask-clip": {
|
||||||
|
// https://drafts.fxtf.org/css-masking-1/#propdef-mask-clip
|
||||||
|
tests: [
|
||||||
|
discrete("content-box", "border-box")
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"mask-composite": {
|
||||||
|
// https://drafts.fxtf.org/css-masking-1/#propdef-mask-composite
|
||||||
|
tests: [
|
||||||
|
discrete("add", "subtract")
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"mask-image": {
|
||||||
|
// https://drafts.fxtf.org/css-masking-1/#propdef-mask-image
|
||||||
|
tests: [
|
||||||
|
discrete("url(\"http://localhost/test-1\")", "url(\"http://localhost/test-2\")")
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"mask-mode": {
|
||||||
|
// https://drafts.fxtf.org/css-masking-1/#propdef-mask-mode
|
||||||
|
tests: [
|
||||||
|
discrete("alpha", "luminance")
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"mask-origin": {
|
||||||
|
// https://drafts.fxtf.org/css-masking-1/#propdef-mask-origin
|
||||||
|
tests: [
|
||||||
|
discrete("content-box", "border-box")
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"mask-repeat": {
|
||||||
|
// https://drafts.fxtf.org/css-masking-1/#propdef-mask-repeat
|
||||||
|
tests: [
|
||||||
|
discrete("space", "round")
|
||||||
|
]
|
||||||
|
},
|
||||||
"mask-type": {
|
"mask-type": {
|
||||||
// https://drafts.fxtf.org/css-masking-1/#propdef-mask-type
|
// https://drafts.fxtf.org/css-masking-1/#propdef-mask-type
|
||||||
tests: [
|
tests: [
|
||||||
discrete("alpha", "luminance")
|
discrete("alpha", "luminance")
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
"mix-blend-mode": {
|
||||||
|
// https://drafts.fxtf.org/compositing-1/#propdef-mix-blend-mode
|
||||||
|
tests: [
|
||||||
|
discrete("multiply", "screen")
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"object-fit": {
|
||||||
|
// https://drafts.csswg.org/css-images-3/#propdef-object-fit
|
||||||
|
tests: [
|
||||||
|
discrete("fill", "contain")
|
||||||
|
]
|
||||||
|
},
|
||||||
"order": {
|
"order": {
|
||||||
// https://drafts.csswg.org/css-flexbox/#propdef-order
|
// https://drafts.csswg.org/css-flexbox/#propdef-order
|
||||||
tests: [
|
tests: [
|
||||||
integer()
|
integer()
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
"outline-style": {
|
||||||
|
// https://drafts.csswg.org/css-ui/#propdef-outline-style
|
||||||
|
tests: [
|
||||||
|
discrete("none", "dotted")
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"overflow-clip-box": {
|
||||||
|
// https://developer.mozilla.org/en/docs/Web/CSS/overflow-clip-box
|
||||||
|
tests: [
|
||||||
|
discrete("padding-box", "content-box")
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"overflow-wrap": {
|
||||||
|
// https://drafts.csswg.org/css-text-3/#propdef-overflow-wrap
|
||||||
|
tests: [
|
||||||
|
discrete("normal", "break-word")
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"overflow-x": {
|
||||||
|
// https://drafts.csswg.org/css-overflow-3/#propdef-overflow-x
|
||||||
|
tests: [
|
||||||
|
discrete("visible", "hidden")
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"overflow-y": {
|
||||||
|
// https://drafts.csswg.org/css-overflow-3/#propdef-overflow-y
|
||||||
|
tests: [
|
||||||
|
discrete("visible", "hidden")
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"page-break-after": {
|
||||||
|
// https://drafts.csswg.org/css-break-3/#propdef-break-after
|
||||||
|
tests: [
|
||||||
|
discrete("always", "auto")
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"page-break-before": {
|
||||||
|
// https://drafts.csswg.org/css-break-3/#propdef-break-before
|
||||||
|
tests: [
|
||||||
|
discrete("always", "auto")
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"page-break-inside": {
|
||||||
|
// https://drafts.csswg.org/css-break-3/#propdef-break-inside
|
||||||
|
tests: [
|
||||||
|
discrete("auto", "avoid")
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"paint-order": {
|
||||||
|
// https://svgwg.org/svg2-draft/painting.html#PaintOrderProperty
|
||||||
|
tests: [
|
||||||
|
discrete("fill", "stroke")
|
||||||
|
]
|
||||||
|
},
|
||||||
"pointer-events": {
|
"pointer-events": {
|
||||||
// https://svgwg.org/svg2-draft/interact.html#PointerEventsProperty
|
// https://svgwg.org/svg2-draft/interact.html#PointerEventsProperty
|
||||||
tests: [
|
tests: [
|
||||||
discrete("fill", "none")
|
discrete("fill", "none")
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
"position": {
|
||||||
|
// https://drafts.csswg.org/css-position/#propdef-position
|
||||||
|
tests: [
|
||||||
|
discrete("absolute", "fixed")
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"quotes": {
|
||||||
|
// https://drafts.csswg.org/css-content-3/#propdef-quotes
|
||||||
|
tests: [
|
||||||
|
discrete("\"“\" \"”\" \"‘\" \"’\"", "\"‘\" \"’\" \"“\" \"”\"")
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"resize": {
|
||||||
|
// https://drafts.csswg.org/css-ui/#propdef-resize
|
||||||
|
tests: [
|
||||||
|
discrete("both", "horizontal")
|
||||||
|
]
|
||||||
|
},
|
||||||
"ruby-align": {
|
"ruby-align": {
|
||||||
// https://drafts.csswg.org/css-ruby-1/#propdef-ruby-align
|
// https://drafts.csswg.org/css-ruby-1/#propdef-ruby-align
|
||||||
tests: [
|
tests: [
|
||||||
|
@ -156,6 +667,30 @@ var gCSSProperties = {
|
||||||
],
|
],
|
||||||
tagName: "ruby"
|
tagName: "ruby"
|
||||||
},
|
},
|
||||||
|
"scroll-behavior": {
|
||||||
|
// https://drafts.csswg.org/cssom-view/#propdef-scroll-behavior
|
||||||
|
tests: [
|
||||||
|
discrete("auto", "smooth")
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"scroll-snap-type-x": {
|
||||||
|
// https://developer.mozilla.org/en/docs/Web/CSS/scroll-snap-type-x
|
||||||
|
tests: [
|
||||||
|
discrete("mandatory", "proximity")
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"scroll-snap-type-y": {
|
||||||
|
// https://developer.mozilla.org/en/docs/Web/CSS/scroll-snap-type-y
|
||||||
|
tests: [
|
||||||
|
discrete("mandatory", "proximity")
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"shape-outside": {
|
||||||
|
// http://dev.w3.org/csswg/css-shapes/#propdef-shape-outside
|
||||||
|
tests: [
|
||||||
|
discrete("url(\"http://localhost/test-1\")", "url(\"http://localhost/test-2\")")
|
||||||
|
]
|
||||||
|
},
|
||||||
"shape-rendering": {
|
"shape-rendering": {
|
||||||
// https://svgwg.org/svg2-draft/painting.html#ShapeRenderingProperty
|
// https://svgwg.org/svg2-draft/painting.html#ShapeRenderingProperty
|
||||||
tests: [
|
tests: [
|
||||||
|
@ -175,6 +710,24 @@ var gCSSProperties = {
|
||||||
],
|
],
|
||||||
tagName: "rect"
|
tagName: "rect"
|
||||||
},
|
},
|
||||||
|
"table-layout": {
|
||||||
|
// https://drafts.csswg.org/css-tables/#propdef-table-layout
|
||||||
|
tests: [
|
||||||
|
discrete("auto", "fixed")
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"text-align": {
|
||||||
|
// https://drafts.csswg.org/css-text-3/#propdef-text-align
|
||||||
|
tests: [
|
||||||
|
discrete("start", "end")
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"text-align-last": {
|
||||||
|
// https://drafts.csswg.org/css-text-3/#propdef-text-align-last
|
||||||
|
tests: [
|
||||||
|
discrete("start", "end")
|
||||||
|
]
|
||||||
|
},
|
||||||
"text-anchor": {
|
"text-anchor": {
|
||||||
// https://svgwg.org/svg2-draft/text.html#TextAnchorProperty
|
// https://svgwg.org/svg2-draft/text.html#TextAnchorProperty
|
||||||
tests: [
|
tests: [
|
||||||
|
@ -184,7 +737,7 @@ var gCSSProperties = {
|
||||||
"text-combine-upright": {
|
"text-combine-upright": {
|
||||||
// https://drafts.csswg.org/css-writing-modes-3/#propdef-text-combine-upright
|
// https://drafts.csswg.org/css-writing-modes-3/#propdef-text-combine-upright
|
||||||
tests: [
|
tests: [
|
||||||
discrete("all", "digits")
|
discrete("all", "none")
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"text-decoration-line": {
|
"text-decoration-line": {
|
||||||
|
@ -193,18 +746,72 @@ var gCSSProperties = {
|
||||||
discrete("underline", "overline")
|
discrete("underline", "overline")
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
"text-decoration-style": {
|
||||||
|
// http://dev.w3.org/csswg/css-text-decor-3/#propdef-text-decoration-style
|
||||||
|
tests: [
|
||||||
|
discrete("solid", "dotted")
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"text-emphasis-position": {
|
||||||
|
// http://dev.w3.org/csswg/css-text-decor-3/#propdef-text-emphasis-position
|
||||||
|
tests: [
|
||||||
|
discrete("over right", "under left")
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"text-emphasis-style": {
|
||||||
|
// http://dev.w3.org/csswg/css-text-decor-3/#propdef-text-emphasis-style
|
||||||
|
tests: [
|
||||||
|
discrete("filled circle", "open dot")
|
||||||
|
]
|
||||||
|
},
|
||||||
"text-orientation": {
|
"text-orientation": {
|
||||||
// https://drafts.csswg.org/css-writing-modes-3/#propdef-text-orientation
|
// https://drafts.csswg.org/css-writing-modes-3/#propdef-text-orientation
|
||||||
tests: [
|
tests: [
|
||||||
discrete("upright", "sideways")
|
discrete("upright", "sideways")
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
"text-overflow": {
|
||||||
|
// https://drafts.csswg.org/css-ui/#propdef-text-overflow
|
||||||
|
tests: [
|
||||||
|
discrete("clip", "ellipsis")
|
||||||
|
]
|
||||||
|
},
|
||||||
"text-rendering": {
|
"text-rendering": {
|
||||||
// https://svgwg.org/svg2-draft/painting.html#TextRenderingProperty
|
// https://svgwg.org/svg2-draft/painting.html#TextRenderingProperty
|
||||||
tests: [
|
tests: [
|
||||||
discrete("optimizeSpeed", "optimizeLegibility")
|
discrete("optimizeSpeed", "optimizeLegibility")
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
"text-transform": {
|
||||||
|
// https://drafts.csswg.org/css-text-3/#propdef-text-transform
|
||||||
|
tests: [
|
||||||
|
discrete("capitalize", "uppercase")
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"touch-action": {
|
||||||
|
// https://w3c.github.io/pointerevents/#the-touch-action-css-property
|
||||||
|
tests: [
|
||||||
|
discrete("auto", "none")
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"transform-box": {
|
||||||
|
// https://drafts.csswg.org/css-transforms/#propdef-transform-box
|
||||||
|
tests: [
|
||||||
|
discrete("fill-box", "border-box")
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"transform-style": {
|
||||||
|
// https://drafts.csswg.org/css-transforms/#propdef-transform-style
|
||||||
|
tests: [
|
||||||
|
discrete("flat", "preserve-3d")
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"unicode-bidi": {
|
||||||
|
// https://drafts.csswg.org/css-writing-modes-3/#propdef-unicode-bidi
|
||||||
|
tests: [
|
||||||
|
discrete("embed", "bidi-override")
|
||||||
|
]
|
||||||
|
},
|
||||||
"vector-effect": {
|
"vector-effect": {
|
||||||
// https://svgwg.org/svg2-draft/coords.html#VectorEffectProperty
|
// https://svgwg.org/svg2-draft/coords.html#VectorEffectProperty
|
||||||
tests: [
|
tests: [
|
||||||
|
@ -217,12 +824,24 @@ var gCSSProperties = {
|
||||||
visibility()
|
visibility()
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
"white-space": {
|
||||||
|
// https://drafts.csswg.org/css-text-4/#propdef-white-space
|
||||||
|
tests: [
|
||||||
|
discrete("pre", "nowrap")
|
||||||
|
]
|
||||||
|
},
|
||||||
"word-break": {
|
"word-break": {
|
||||||
// https://drafts.csswg.org/css-text-3/#propdef-word-break
|
// https://drafts.csswg.org/css-text-3/#propdef-word-break
|
||||||
tests: [
|
tests: [
|
||||||
discrete("keep-all", "break-all")
|
discrete("keep-all", "break-all")
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
"will-change": {
|
||||||
|
// http://dev.w3.org/csswg/css-will-change/#propdef-will-change
|
||||||
|
tests: [
|
||||||
|
discrete("scroll-position", "contents")
|
||||||
|
]
|
||||||
|
},
|
||||||
"writing-mode": {
|
"writing-mode": {
|
||||||
// https://drafts.csswg.org/css-writing-modes-3/#propdef-writing-mode
|
// https://drafts.csswg.org/css-writing-modes-3/#propdef-writing-mode
|
||||||
tests: [
|
tests: [
|
||||||
|
@ -247,7 +866,7 @@ function discrete(from, to) {
|
||||||
var idlName = propertyToIDL(property);
|
var idlName = propertyToIDL(property);
|
||||||
var keyframes = {};
|
var keyframes = {};
|
||||||
keyframes[idlName] = [from, to];
|
keyframes[idlName] = [from, to];
|
||||||
var target = createElement(t, options.tagName);
|
var target = createTestElement(t, options.tagName);
|
||||||
var animation = target.animate(keyframes,
|
var animation = target.animate(keyframes,
|
||||||
{ duration: 1000, fill: "both" });
|
{ duration: 1000, fill: "both" });
|
||||||
testAnimationSamples(animation, idlName,
|
testAnimationSamples(animation, idlName,
|
||||||
|
@ -265,7 +884,7 @@ function discrete(from, to) {
|
||||||
var idlName = propertyToIDL(property);
|
var idlName = propertyToIDL(property);
|
||||||
var keyframes = {};
|
var keyframes = {};
|
||||||
keyframes[idlName] = [from, to];
|
keyframes[idlName] = [from, to];
|
||||||
var target = createElement(t, options.tagName);
|
var target = createTestElement(t, options.tagName);
|
||||||
var animation = target.animate(keyframes,
|
var animation = target.animate(keyframes,
|
||||||
{ duration: 1000, fill: "both",
|
{ duration: 1000, fill: "both",
|
||||||
easing: "cubic-bezier(0.68,0,1,0.01)" });
|
easing: "cubic-bezier(0.68,0,1,0.01)" });
|
||||||
|
@ -284,7 +903,7 @@ function discrete(from, to) {
|
||||||
var keyframes = {};
|
var keyframes = {};
|
||||||
keyframes[idlName] = [from, to];
|
keyframes[idlName] = [from, to];
|
||||||
keyframes.easing = "cubic-bezier(0.68,0,1,0.01)";
|
keyframes.easing = "cubic-bezier(0.68,0,1,0.01)";
|
||||||
var target = createElement(t, options.tagName);
|
var target = createTestElement(t, options.tagName);
|
||||||
var animation = target.animate(keyframes,
|
var animation = target.animate(keyframes,
|
||||||
{ duration: 1000, fill: "both" });
|
{ duration: 1000, fill: "both" });
|
||||||
testAnimationSamples(animation, idlName,
|
testAnimationSamples(animation, idlName,
|
||||||
|
@ -302,7 +921,7 @@ function length() {
|
||||||
var idlName = propertyToIDL(property);
|
var idlName = propertyToIDL(property);
|
||||||
var keyframes = {};
|
var keyframes = {};
|
||||||
keyframes[idlName] = ["10px", "50px"];
|
keyframes[idlName] = ["10px", "50px"];
|
||||||
var target = createElement(t, options.tagName);
|
var target = createTestElement(t, options.tagName);
|
||||||
var animation = target.animate(keyframes,
|
var animation = target.animate(keyframes,
|
||||||
{ duration: 1000, fill: "both" });
|
{ duration: 1000, fill: "both" });
|
||||||
testAnimationSamples(animation, idlName,
|
testAnimationSamples(animation, idlName,
|
||||||
|
@ -315,7 +934,7 @@ function length() {
|
||||||
var idlName = propertyToIDL(property);
|
var idlName = propertyToIDL(property);
|
||||||
var keyframes = {};
|
var keyframes = {};
|
||||||
keyframes[idlName] = ["1rem", "5rem"];
|
keyframes[idlName] = ["1rem", "5rem"];
|
||||||
var target = createElement(t, options.tagName);
|
var target = createTestElement(t, options.tagName);
|
||||||
var animation = target.animate(keyframes,
|
var animation = target.animate(keyframes,
|
||||||
{ duration: 1000, fill: "both" });
|
{ duration: 1000, fill: "both" });
|
||||||
testAnimationSamples(animation, idlName,
|
testAnimationSamples(animation, idlName,
|
||||||
|
@ -332,7 +951,7 @@ function percentage() {
|
||||||
var idlName = propertyToIDL(property);
|
var idlName = propertyToIDL(property);
|
||||||
var keyframes = {};
|
var keyframes = {};
|
||||||
keyframes[idlName] = ["10%", "50%"];
|
keyframes[idlName] = ["10%", "50%"];
|
||||||
var target = createElement(t, options.tagName);
|
var target = createTestElement(t, options.tagName);
|
||||||
var animation = target.animate(keyframes,
|
var animation = target.animate(keyframes,
|
||||||
{ duration: 1000, fill: "both" });
|
{ duration: 1000, fill: "both" });
|
||||||
testAnimationSamples(animation, idlName,
|
testAnimationSamples(animation, idlName,
|
||||||
|
@ -349,7 +968,7 @@ function integer() {
|
||||||
var idlName = propertyToIDL(property);
|
var idlName = propertyToIDL(property);
|
||||||
var keyframes = {};
|
var keyframes = {};
|
||||||
keyframes[idlName] = [-2, 2];
|
keyframes[idlName] = [-2, 2];
|
||||||
var target = createElement(t, options.tagName);
|
var target = createTestElement(t, options.tagName);
|
||||||
var animation = target.animate(keyframes,
|
var animation = target.animate(keyframes,
|
||||||
{ duration: 1000, fill: "both" });
|
{ duration: 1000, fill: "both" });
|
||||||
testAnimationSamples(animation, idlName,
|
testAnimationSamples(animation, idlName,
|
||||||
|
@ -366,7 +985,7 @@ function positiveNumber() {
|
||||||
var idlName = propertyToIDL(property);
|
var idlName = propertyToIDL(property);
|
||||||
var keyframes = {};
|
var keyframes = {};
|
||||||
keyframes[idlName] = [1.1, 1.5];
|
keyframes[idlName] = [1.1, 1.5];
|
||||||
var target = createElement(t, options.tagName);
|
var target = createTestElement(t, options.tagName);
|
||||||
var animation = target.animate(keyframes,
|
var animation = target.animate(keyframes,
|
||||||
{ duration: 1000, fill: "both" });
|
{ duration: 1000, fill: "both" });
|
||||||
testAnimationSamples(animation, idlName,
|
testAnimationSamples(animation, idlName,
|
||||||
|
@ -386,7 +1005,7 @@ function lengthPercentageOrCalc() {
|
||||||
var idlName = propertyToIDL(property);
|
var idlName = propertyToIDL(property);
|
||||||
var keyframes = {};
|
var keyframes = {};
|
||||||
keyframes[idlName] = ["10px", "20%"];
|
keyframes[idlName] = ["10px", "20%"];
|
||||||
var target = createElement(t, options.tagName);
|
var target = createTestElement(t, options.tagName);
|
||||||
var animation = target.animate(keyframes,
|
var animation = target.animate(keyframes,
|
||||||
{ duration: 1000, fill: "both" });
|
{ duration: 1000, fill: "both" });
|
||||||
testAnimationSamples(animation, idlName,
|
testAnimationSamples(animation, idlName,
|
||||||
|
@ -399,7 +1018,7 @@ function lengthPercentageOrCalc() {
|
||||||
var idlName = propertyToIDL(property);
|
var idlName = propertyToIDL(property);
|
||||||
var keyframes = {};
|
var keyframes = {};
|
||||||
keyframes[idlName] = ["10%", "2em"];
|
keyframes[idlName] = ["10%", "2em"];
|
||||||
var target = createElement(t, options.tagName);
|
var target = createTestElement(t, options.tagName);
|
||||||
var animation = target.animate(keyframes,
|
var animation = target.animate(keyframes,
|
||||||
{ duration: 1000, fill: "both" });
|
{ duration: 1000, fill: "both" });
|
||||||
testAnimationSamples(animation, idlName,
|
testAnimationSamples(animation, idlName,
|
||||||
|
@ -412,7 +1031,7 @@ function lengthPercentageOrCalc() {
|
||||||
var idlName = propertyToIDL(property);
|
var idlName = propertyToIDL(property);
|
||||||
var keyframes = {};
|
var keyframes = {};
|
||||||
keyframes[idlName] = ["1em", "2rem"];
|
keyframes[idlName] = ["1em", "2rem"];
|
||||||
var target = createElement(t, options.tagName);
|
var target = createTestElement(t, options.tagName);
|
||||||
var animation = target.animate(keyframes,
|
var animation = target.animate(keyframes,
|
||||||
{ duration: 1000, fill: "both" });
|
{ duration: 1000, fill: "both" });
|
||||||
testAnimationSamples(animation, idlName,
|
testAnimationSamples(animation, idlName,
|
||||||
|
@ -425,7 +1044,7 @@ function lengthPercentageOrCalc() {
|
||||||
var idlName = propertyToIDL(property);
|
var idlName = propertyToIDL(property);
|
||||||
var keyframes = {};
|
var keyframes = {};
|
||||||
keyframes[idlName] = ["10px", "calc(1em + 20%)"];
|
keyframes[idlName] = ["10px", "calc(1em + 20%)"];
|
||||||
var target = createElement(t, options.tagName);
|
var target = createTestElement(t, options.tagName);
|
||||||
var animation = target.animate(keyframes,
|
var animation = target.animate(keyframes,
|
||||||
{ duration: 1000, fill: "both" });
|
{ duration: 1000, fill: "both" });
|
||||||
testAnimationSamples(animation, idlName,
|
testAnimationSamples(animation, idlName,
|
||||||
|
@ -438,7 +1057,7 @@ function lengthPercentageOrCalc() {
|
||||||
var idlName = propertyToIDL(property);
|
var idlName = propertyToIDL(property);
|
||||||
var keyframes = {};
|
var keyframes = {};
|
||||||
keyframes[idlName] = ["calc(10px + 10%)", "calc(1em + 1rem + 20%)"];
|
keyframes[idlName] = ["calc(10px + 10%)", "calc(1em + 1rem + 20%)"];
|
||||||
var target = createElement(t, options.tagName);
|
var target = createTestElement(t, options.tagName);
|
||||||
var animation = target.animate(keyframes,
|
var animation = target.animate(keyframes,
|
||||||
{ duration: 1000, fill: "both" });
|
{ duration: 1000, fill: "both" });
|
||||||
testAnimationSamples(animation, idlName,
|
testAnimationSamples(animation, idlName,
|
||||||
|
@ -458,7 +1077,7 @@ function visibility() {
|
||||||
var idlName = propertyToIDL(property);
|
var idlName = propertyToIDL(property);
|
||||||
var keyframes = {};
|
var keyframes = {};
|
||||||
keyframes[idlName] = ["visible", "hidden"];
|
keyframes[idlName] = ["visible", "hidden"];
|
||||||
var target = createElement(t, options.tagName);
|
var target = createTestElement(t, options.tagName);
|
||||||
var animation = target.animate(keyframes,
|
var animation = target.animate(keyframes,
|
||||||
{ duration: 1000, fill: "both" });
|
{ duration: 1000, fill: "both" });
|
||||||
testAnimationSamples(animation, idlName,
|
testAnimationSamples(animation, idlName,
|
||||||
|
@ -472,7 +1091,7 @@ function visibility() {
|
||||||
var idlName = propertyToIDL(property);
|
var idlName = propertyToIDL(property);
|
||||||
var keyframes = {};
|
var keyframes = {};
|
||||||
keyframes[idlName] = ["hidden", "visible"];
|
keyframes[idlName] = ["hidden", "visible"];
|
||||||
var target = createElement(t, options.tagName);
|
var target = createTestElement(t, options.tagName);
|
||||||
var animation = target.animate(keyframes,
|
var animation = target.animate(keyframes,
|
||||||
{ duration: 1000, fill: "both" });
|
{ duration: 1000, fill: "both" });
|
||||||
testAnimationSamples(animation, idlName,
|
testAnimationSamples(animation, idlName,
|
||||||
|
@ -486,7 +1105,7 @@ function visibility() {
|
||||||
var idlName = propertyToIDL(property);
|
var idlName = propertyToIDL(property);
|
||||||
var keyframes = {};
|
var keyframes = {};
|
||||||
keyframes[idlName] = ["hidden", "collapse"];
|
keyframes[idlName] = ["hidden", "collapse"];
|
||||||
var target = createElement(t, options.tagName);
|
var target = createTestElement(t, options.tagName);
|
||||||
var animation = target.animate(keyframes,
|
var animation = target.animate(keyframes,
|
||||||
{ duration: 1000, fill: "both" });
|
{ duration: 1000, fill: "both" });
|
||||||
testAnimationSamples(animation, idlName,
|
testAnimationSamples(animation, idlName,
|
||||||
|
@ -504,7 +1123,7 @@ function visibility() {
|
||||||
var idlName = propertyToIDL(property);
|
var idlName = propertyToIDL(property);
|
||||||
var keyframes = {};
|
var keyframes = {};
|
||||||
keyframes[idlName] = ["visible", "hidden"];
|
keyframes[idlName] = ["visible", "hidden"];
|
||||||
var target = createElement(t, options.tagName);
|
var target = createTestElement(t, options.tagName);
|
||||||
var animation =
|
var animation =
|
||||||
target.animate(keyframes,
|
target.animate(keyframes,
|
||||||
{ duration: 1000, fill: "both",
|
{ duration: 1000, fill: "both",
|
||||||
|
@ -523,15 +1142,25 @@ function visibility() {
|
||||||
}
|
}
|
||||||
|
|
||||||
function testAnimationSamples(animation, idlName, testSamples) {
|
function testAnimationSamples(animation, idlName, testSamples) {
|
||||||
var target = animation.effect.target;
|
var type = animation.effect.target.type;
|
||||||
|
var target = type
|
||||||
|
? animation.effect.target.parentElement
|
||||||
|
: animation.effect.target;
|
||||||
testSamples.forEach(function(testSample) {
|
testSamples.forEach(function(testSample) {
|
||||||
animation.currentTime = testSample.time;
|
animation.currentTime = testSample.time;
|
||||||
assert_equals(getComputedStyle(target)[idlName], testSample.expected,
|
assert_equals(getComputedStyle(target, type)[idlName],
|
||||||
|
testSample.expected,
|
||||||
"The value should be " + testSample.expected +
|
"The value should be " + testSample.expected +
|
||||||
" at " + testSample.time + "ms");
|
" at " + testSample.time + "ms");
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function createTestElement(t, tagName) {
|
||||||
|
return tagName && tagName.startsWith("::")
|
||||||
|
? createPseudo(t, tagName.substring(2))
|
||||||
|
: createElement(t, tagName);
|
||||||
|
}
|
||||||
|
|
||||||
function isSupported(property) {
|
function isSupported(property) {
|
||||||
var testKeyframe = new TestKeyframe(propertyToIDL(property));
|
var testKeyframe = new TestKeyframe(propertyToIDL(property));
|
||||||
try {
|
try {
|
||||||
|
|
|
@ -0,0 +1,292 @@
|
||||||
|
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||||
|
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
||||||
|
* You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||||
|
|
||||||
|
"use strict";
|
||||||
|
|
||||||
|
this.EXPORTED_SYMBOLS = [ "ExtensionSearchHandler" ];
|
||||||
|
|
||||||
|
// Used to keep track of all of the registered keywords, where each keyword is
|
||||||
|
// mapped to a KeywordInfo instance.
|
||||||
|
let gKeywordMap = new Map();
|
||||||
|
|
||||||
|
// Used to keep track of the active input session.
|
||||||
|
let gActiveInputSession = null;
|
||||||
|
|
||||||
|
// Used to keep track of who has control over the active suggestion callback
|
||||||
|
// so older callbacks can be ignored. The callback ID should increment whenever
|
||||||
|
// the input changes or the input session ends.
|
||||||
|
let gCurrentCallbackID = 0;
|
||||||
|
|
||||||
|
// Handles keeping track of information associated to the registered keyword.
|
||||||
|
class KeywordInfo {
|
||||||
|
constructor(extension, description) {
|
||||||
|
this._extension = extension;
|
||||||
|
this._description = description;
|
||||||
|
}
|
||||||
|
|
||||||
|
get description() {
|
||||||
|
return this._description;
|
||||||
|
}
|
||||||
|
|
||||||
|
set description(desc) {
|
||||||
|
this._description = desc;
|
||||||
|
}
|
||||||
|
|
||||||
|
get extension() {
|
||||||
|
return this._extension;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Responsible for handling communication between the extension and the urlbar.
|
||||||
|
class InputSession {
|
||||||
|
constructor(keyword, extension) {
|
||||||
|
this._keyword = keyword;
|
||||||
|
this._extension = extension;
|
||||||
|
this._suggestionsCallback = null;
|
||||||
|
this._searchFinishedCallback = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
get keyword() {
|
||||||
|
return this._keyword;
|
||||||
|
}
|
||||||
|
|
||||||
|
addSuggestions(suggestions) {
|
||||||
|
this._suggestionsCallback(suggestions);
|
||||||
|
}
|
||||||
|
|
||||||
|
start(eventName) {
|
||||||
|
this._extension.emit(eventName);
|
||||||
|
}
|
||||||
|
|
||||||
|
update(eventName, text, suggestionsCallback, searchFinishedCallback) {
|
||||||
|
if (this._searchFinishedCallback) {
|
||||||
|
this._searchFinishedCallback();
|
||||||
|
}
|
||||||
|
this._searchFinishedCallback = searchFinishedCallback;
|
||||||
|
this._suggestionsCallback = suggestionsCallback;
|
||||||
|
this._extension.emit(eventName, text, ++gCurrentCallbackID);
|
||||||
|
}
|
||||||
|
|
||||||
|
cancel(eventName) {
|
||||||
|
this._searchFinishedCallback();
|
||||||
|
this._extension.emit(eventName);
|
||||||
|
}
|
||||||
|
|
||||||
|
end(eventName, text, disposition) {
|
||||||
|
this._searchFinishedCallback();
|
||||||
|
this._extension.emit(eventName, text, disposition);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var ExtensionSearchHandler = Object.freeze({
|
||||||
|
MSG_INPUT_STARTED: "webext-omnibox-input-started",
|
||||||
|
MSG_INPUT_CHANGED: "webext-omnibox-input-changed",
|
||||||
|
MSG_INPUT_ENTERED: "webext-omnibox-input-entered",
|
||||||
|
MSG_INPUT_CANCELLED: "webext-omnibox-input-cancelled",
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Registers a keyword.
|
||||||
|
*
|
||||||
|
* @param {string} keyword The keyword to register.
|
||||||
|
* @param {Extension} extension The extension registering the keyword.
|
||||||
|
*/
|
||||||
|
registerKeyword(keyword, extension) {
|
||||||
|
if (gKeywordMap.has(keyword)) {
|
||||||
|
throw new Error(`The keyword provided is already registered: "${keyword}"`);
|
||||||
|
}
|
||||||
|
gKeywordMap.set(keyword, new KeywordInfo(extension, extension.name));
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Unregisters a keyword.
|
||||||
|
*
|
||||||
|
* @param {string} keyword The keyword to unregister.
|
||||||
|
*/
|
||||||
|
unregisterKeyword(keyword) {
|
||||||
|
if (!gKeywordMap.has(keyword)) {
|
||||||
|
throw new Error(`The keyword provided is not registered: "${keyword}"`);
|
||||||
|
}
|
||||||
|
gActiveInputSession = null;
|
||||||
|
gKeywordMap.delete(keyword);
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks if a keyword is registered.
|
||||||
|
*
|
||||||
|
* @param {string} keyword The word to check.
|
||||||
|
* @return {boolean} true if the word is a registered keyword.
|
||||||
|
*/
|
||||||
|
isKeywordRegistered(keyword) {
|
||||||
|
return gKeywordMap.has(keyword);
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return {boolean} true if there is an active input session.
|
||||||
|
*/
|
||||||
|
hasActiveInputSession() {
|
||||||
|
return gActiveInputSession != null;
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {string} keyword The keyword to look up.
|
||||||
|
* @return {string} the description to use for the heuristic result.
|
||||||
|
*/
|
||||||
|
getDescription(keyword) {
|
||||||
|
if (!gKeywordMap.has(keyword)) {
|
||||||
|
throw new Error(`The keyword provided is not registered: "${keyword}"`);
|
||||||
|
}
|
||||||
|
return gKeywordMap.get(keyword).description;
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the default suggestion for the registered keyword. The suggestion's
|
||||||
|
* description will be used for the comment in the heuristic result.
|
||||||
|
*
|
||||||
|
* @param {string} keyword The keyword.
|
||||||
|
* @param {string} description The description to use for the heuristic result.
|
||||||
|
*/
|
||||||
|
setDefaultSuggestion(keyword, {description}) {
|
||||||
|
if (!gKeywordMap.has(keyword)) {
|
||||||
|
throw new Error(`The keyword provided is not registered: "${keyword}"`);
|
||||||
|
}
|
||||||
|
gKeywordMap.get(keyword).description = description;
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds suggestions for the registered keyword. This function will throw if
|
||||||
|
* the keyword provided is not registered or active, or if the callback ID
|
||||||
|
* provided is no longer equal to the active callback ID.
|
||||||
|
*
|
||||||
|
* @param {string} keyword The keyword.
|
||||||
|
* @param {integer} id The ID of the suggestion callback.
|
||||||
|
* @param {Array<Object>} suggestions An array of suggestions to provide to the urlbar.
|
||||||
|
*/
|
||||||
|
addSuggestions(keyword, id, suggestions) {
|
||||||
|
if (!gKeywordMap.has(keyword)) {
|
||||||
|
throw new Error(`The keyword provided is not registered: "${keyword}"`);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!gActiveInputSession || gActiveInputSession.keyword != keyword) {
|
||||||
|
throw new Error(`The keyword provided is not apart of an active input session: "${keyword}"`);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (id != gCurrentCallbackID) {
|
||||||
|
throw new Error(`The callback is no longer active for the keyword provided: "${keyword}"`);
|
||||||
|
}
|
||||||
|
|
||||||
|
gActiveInputSession.addSuggestions(suggestions);
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called when the input in the urlbar begins with `<keyword><space>`.
|
||||||
|
*
|
||||||
|
* If the keyword is inactive, MSG_INPUT_STARTED is emitted and the
|
||||||
|
* keyword is marked as active. If the keyword is followed by any text,
|
||||||
|
* MSG_INPUT_CHANGED is fired with the current callback ID that can be
|
||||||
|
* used to provide suggestions to the urlbar while the callback ID is active.
|
||||||
|
* The callback is invalidated when either the input changes or the urlbar blurs.
|
||||||
|
*
|
||||||
|
* @param {string} keyword The keyword to handle.
|
||||||
|
* @param {string} text The search text in the urlbar.
|
||||||
|
* @param {Function} callback The callback used to provide search suggestions.
|
||||||
|
* @return {Promise} promise that resolves when the current search is complete.
|
||||||
|
*/
|
||||||
|
handleSearch(keyword, text, callback) {
|
||||||
|
if (!gKeywordMap.has(keyword)) {
|
||||||
|
throw new Error(`The keyword provided is not registered: "${keyword}"`);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (gActiveInputSession && gActiveInputSession.keyword != keyword) {
|
||||||
|
throw new Error("A different input session is already ongoing");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!text || !text.startsWith(`${keyword} `)) {
|
||||||
|
throw new Error(`The text provided must start with: "${keyword} "`);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!callback) {
|
||||||
|
throw new Error("A callback must be provided");
|
||||||
|
}
|
||||||
|
|
||||||
|
// The search text in the urlbar currently starts with <keyword><space>, and
|
||||||
|
// we only want the text that follows.
|
||||||
|
text = text.substring(keyword.length + 1);
|
||||||
|
|
||||||
|
// We fire MSG_INPUT_STARTED once we have <keyword><space>, and only fire
|
||||||
|
// MSG_INPUT_CHANGED when we have text to process. This is different from Chrome's
|
||||||
|
// behavior, which always fires MSG_INPUT_STARTED right before MSG_INPUT_CHANGED
|
||||||
|
// first fires, but this is a bug in Chrome according to https://crbug.com/258911.
|
||||||
|
if (!gActiveInputSession) {
|
||||||
|
gActiveInputSession = new InputSession(keyword, gKeywordMap.get(keyword).extension);
|
||||||
|
gActiveInputSession.start(this.MSG_INPUT_STARTED);
|
||||||
|
|
||||||
|
// Resolve early if there is no text to process. There can be text to process when
|
||||||
|
// the input starts if the user copy/pastes the text into the urlbar.
|
||||||
|
if (!text.length) {
|
||||||
|
return Promise.resolve();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return new Promise(resolve => {
|
||||||
|
gActiveInputSession.update(this.MSG_INPUT_CHANGED, text, callback, resolve);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called when the user clicks on a suggestion that was added by
|
||||||
|
* an extension. MSG_INPUT_ENTERED is emitted to the extension with
|
||||||
|
* the keyword, the current search string, and info about how the
|
||||||
|
* the search should be handled. This ends the active input session.
|
||||||
|
*
|
||||||
|
* @param {string} keyword The keyword associated to the suggestion.
|
||||||
|
* @param {string} text The search text in the urlbar.
|
||||||
|
* @param {string} where How the page should be opened. Accepted values are:
|
||||||
|
* "current": open the page in the same tab.
|
||||||
|
* "tab": open the page in a new foreground tab.
|
||||||
|
* "tabshifted": open the page in a new background tab.
|
||||||
|
*/
|
||||||
|
handleInputEntered(keyword, text, where) {
|
||||||
|
if (!gKeywordMap.has(keyword)) {
|
||||||
|
throw new Error(`The keyword provided is not registered: "${keyword}"`);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (gActiveInputSession && gActiveInputSession.keyword != keyword) {
|
||||||
|
throw new Error("A different input session is already ongoing");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!text || !text.startsWith(`${keyword} `)) {
|
||||||
|
throw new Error(`The text provided must start with: "${keyword} "`);
|
||||||
|
}
|
||||||
|
|
||||||
|
let dispositionMap = {
|
||||||
|
current: "currentTab",
|
||||||
|
tab: "newForegroundTab",
|
||||||
|
tabshifted: "newBackgroundTab",
|
||||||
|
}
|
||||||
|
let disposition = dispositionMap[where];
|
||||||
|
|
||||||
|
if (!disposition) {
|
||||||
|
throw new Error(`Invalid "where" argument: ${where}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
// The search text in the urlbar currently starts with <keyword><space>, and
|
||||||
|
// we only want to send the text that follows.
|
||||||
|
text = text.substring(keyword.length + 1);
|
||||||
|
|
||||||
|
gActiveInputSession.end(this.MSG_INPUT_ENTERED, text, disposition)
|
||||||
|
gActiveInputSession = null;
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* If the user has ended the keyword input session without accepting the input,
|
||||||
|
* MSG_INPUT_CANCELLED is emitted and the input session is ended.
|
||||||
|
*/
|
||||||
|
handleInputCancelled() {
|
||||||
|
if (!gActiveInputSession) {
|
||||||
|
throw new Error("There is no active input session");
|
||||||
|
}
|
||||||
|
gActiveInputSession.cancel(this.MSG_INPUT_CANCELLED);
|
||||||
|
gActiveInputSession = null;
|
||||||
|
}
|
||||||
|
});
|
|
@ -69,6 +69,11 @@ const FRECENCY_DEFAULT = 1000;
|
||||||
// always try to have at least MINIMUM_LOCAL_MATCHES local matches.
|
// always try to have at least MINIMUM_LOCAL_MATCHES local matches.
|
||||||
const MINIMUM_LOCAL_MATCHES = 6;
|
const MINIMUM_LOCAL_MATCHES = 6;
|
||||||
|
|
||||||
|
// Extensions are allowed to add suggestions if they have registered a keyword
|
||||||
|
// with the omnibox API. This is the maximum number of suggestions an extension
|
||||||
|
// is allowed to add for a given search string.
|
||||||
|
const MAXIMUM_ALLOWED_EXTENSION_MATCHES = 6;
|
||||||
|
|
||||||
// A regex that matches "single word" hostnames for whitelisting purposes.
|
// A regex that matches "single word" hostnames for whitelisting purposes.
|
||||||
// The hostname will already have been checked for general validity, so we
|
// The hostname will already have been checked for general validity, so we
|
||||||
// don't need to be exhaustive here, so allow dashes anywhere.
|
// don't need to be exhaustive here, so allow dashes anywhere.
|
||||||
|
@ -269,6 +274,8 @@ XPCOMUtils.defineLazyModuleGetter(this, "OS",
|
||||||
"resource://gre/modules/osfile.jsm");
|
"resource://gre/modules/osfile.jsm");
|
||||||
XPCOMUtils.defineLazyModuleGetter(this, "PromiseUtils",
|
XPCOMUtils.defineLazyModuleGetter(this, "PromiseUtils",
|
||||||
"resource://gre/modules/PromiseUtils.jsm");
|
"resource://gre/modules/PromiseUtils.jsm");
|
||||||
|
XPCOMUtils.defineLazyModuleGetter(this, "ExtensionSearchHandler",
|
||||||
|
"resource://gre/modules/ExtensionSearchHandler.jsm");
|
||||||
XPCOMUtils.defineLazyModuleGetter(this, "Task",
|
XPCOMUtils.defineLazyModuleGetter(this, "Task",
|
||||||
"resource://gre/modules/Task.jsm");
|
"resource://gre/modules/Task.jsm");
|
||||||
XPCOMUtils.defineLazyModuleGetter(this, "PlacesSearchAutocompleteProvider",
|
XPCOMUtils.defineLazyModuleGetter(this, "PlacesSearchAutocompleteProvider",
|
||||||
|
@ -743,11 +750,16 @@ function Search(searchString, searchParam, autocompleteListener,
|
||||||
|
|
||||||
// The index to insert remote matches at.
|
// The index to insert remote matches at.
|
||||||
this._remoteMatchesStartIndex = 0;
|
this._remoteMatchesStartIndex = 0;
|
||||||
|
// The index to insert local matches at.
|
||||||
|
|
||||||
|
this._localMatchesStartIndex = 0;
|
||||||
|
|
||||||
// Counts the number of inserted local matches.
|
// Counts the number of inserted local matches.
|
||||||
this._localMatchesCount = 0;
|
this._localMatchesCount = 0;
|
||||||
// Counts the number of inserted remote matches.
|
// Counts the number of inserted remote matches.
|
||||||
this._remoteMatchesCount = 0;
|
this._remoteMatchesCount = 0;
|
||||||
|
// Counts the number of inserted extension matches.
|
||||||
|
this._extensionMatchesCount = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
Search.prototype = {
|
Search.prototype = {
|
||||||
|
@ -970,7 +982,19 @@ Search.prototype = {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Ensure to fill any remaining space.
|
// Only add extension suggestions if the first token is a registered keyword
|
||||||
|
// and the search string has characters after the first token.
|
||||||
|
if (ExtensionSearchHandler.isKeywordRegistered(this._searchTokens[0]) &&
|
||||||
|
this._originalSearchString.length > this._searchTokens[0].length) {
|
||||||
|
yield this._matchExtensionSuggestions();
|
||||||
|
if (!this.pending)
|
||||||
|
return;
|
||||||
|
} else if (ExtensionSearchHandler.hasActiveInputSession()) {
|
||||||
|
ExtensionSearchHandler.handleInputCancelled();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensure to fill any remaining space. Suggestions which come from extensions are
|
||||||
|
// inserted at the beginning, so any suggestions
|
||||||
yield Promise.all(this._remoteMatchesPromises);
|
yield Promise.all(this._remoteMatchesPromises);
|
||||||
}),
|
}),
|
||||||
|
|
||||||
|
@ -978,7 +1002,15 @@ Search.prototype = {
|
||||||
// We always try to make the first result a special "heuristic" result. The
|
// We always try to make the first result a special "heuristic" result. The
|
||||||
// heuristics below determine what type of result it will be, if any.
|
// heuristics below determine what type of result it will be, if any.
|
||||||
|
|
||||||
let hasSearchTerms = this._searchTokens.length > 0 ;
|
let hasSearchTerms = this._searchTokens.length > 0;
|
||||||
|
|
||||||
|
if (hasSearchTerms) {
|
||||||
|
// It may be a keyword registered by an extension.
|
||||||
|
let matched = yield this._matchExtensionHeuristicResult();
|
||||||
|
if (matched) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (this._enableActions && hasSearchTerms) {
|
if (this._enableActions && hasSearchTerms) {
|
||||||
// It may be a search engine with an alias - which works like a keyword.
|
// It may be a search engine with an alias - which works like a keyword.
|
||||||
|
@ -1152,6 +1184,16 @@ Search.prototype = {
|
||||||
return gotResult;
|
return gotResult;
|
||||||
},
|
},
|
||||||
|
|
||||||
|
_matchExtensionHeuristicResult: function* () {
|
||||||
|
if (ExtensionSearchHandler.isKeywordRegistered(this._searchTokens[0]) &&
|
||||||
|
this._originalSearchString.length > this._searchTokens[0].length) {
|
||||||
|
let description = ExtensionSearchHandler.getDescription(this._searchTokens[0]);
|
||||||
|
this._addExtensionMatch(this._originalSearchString, description);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
},
|
||||||
|
|
||||||
_matchPlacesKeyword: function* () {
|
_matchPlacesKeyword: function* () {
|
||||||
// The first word could be a keyword, so that's what we'll search.
|
// The first word could be a keyword, so that's what we'll search.
|
||||||
let keyword = this._searchTokens[0];
|
let keyword = this._searchTokens[0];
|
||||||
|
@ -1264,6 +1306,24 @@ Search.prototype = {
|
||||||
return true;
|
return true;
|
||||||
},
|
},
|
||||||
|
|
||||||
|
_addExtensionMatch(content, comment) {
|
||||||
|
if (this._extensionMatchesCount >= MAXIMUM_ALLOWED_EXTENSION_MATCHES) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this._addMatch({
|
||||||
|
value: PlacesUtils.mozActionURI("extension", {
|
||||||
|
content,
|
||||||
|
keyword: this._searchTokens[0]
|
||||||
|
}),
|
||||||
|
comment,
|
||||||
|
icon: "chrome://browser/content/extension.svg",
|
||||||
|
style: "action extension",
|
||||||
|
frecency: FRECENCY_DEFAULT,
|
||||||
|
extension: true,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
_addSearchEngineMatch(match, query, suggestion) {
|
_addSearchEngineMatch(match, query, suggestion) {
|
||||||
let actionURLParams = {
|
let actionURLParams = {
|
||||||
engineName: match.engineName,
|
engineName: match.engineName,
|
||||||
|
@ -1287,6 +1347,18 @@ Search.prototype = {
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
|
*_matchExtensionSuggestions() {
|
||||||
|
let promise = ExtensionSearchHandler.handleSearch(this._searchTokens[0], this._originalSearchString,
|
||||||
|
suggestions => {
|
||||||
|
suggestions.forEach(suggestion => {
|
||||||
|
let content = `${this._searchTokens[0]} ${suggestion.content}`;
|
||||||
|
this._addExtensionMatch(content, suggestion.description);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
);
|
||||||
|
this._remoteMatchesPromises.push(promise);
|
||||||
|
},
|
||||||
|
|
||||||
*_matchRemoteTabs() {
|
*_matchRemoteTabs() {
|
||||||
let matches = yield PlacesRemoteTabsAutocompleteProvider.getMatches(this._originalSearchString);
|
let matches = yield PlacesRemoteTabsAutocompleteProvider.getMatches(this._originalSearchString);
|
||||||
for (let {url, title, icon, deviceName} of matches) {
|
for (let {url, title, icon, deviceName} of matches) {
|
||||||
|
@ -1492,6 +1564,11 @@ Search.prototype = {
|
||||||
// Append after local matches.
|
// Append after local matches.
|
||||||
index = this._remoteMatchesStartIndex + this._remoteMatchesCount;
|
index = this._remoteMatchesStartIndex + this._remoteMatchesCount;
|
||||||
this._remoteMatchesCount++;
|
this._remoteMatchesCount++;
|
||||||
|
} else if (match.extension) {
|
||||||
|
index = this._localMatchesStartIndex;
|
||||||
|
this._localMatchesStartIndex++;
|
||||||
|
this._remoteMatchesStartIndex++;
|
||||||
|
this._extensionMatchesCount++;
|
||||||
} else {
|
} else {
|
||||||
// This is a local match.
|
// This is a local match.
|
||||||
if (match.frecency > FRECENCY_DEFAULT ||
|
if (match.frecency > FRECENCY_DEFAULT ||
|
||||||
|
|
|
@ -64,6 +64,7 @@ if CONFIG['MOZ_PLACES']:
|
||||||
'ClusterLib.js',
|
'ClusterLib.js',
|
||||||
'ColorAnalyzer_worker.js',
|
'ColorAnalyzer_worker.js',
|
||||||
'ColorConversion.js',
|
'ColorConversion.js',
|
||||||
|
'ExtensionSearchHandler.jsm',
|
||||||
'History.jsm',
|
'History.jsm',
|
||||||
'PlacesBackups.jsm',
|
'PlacesBackups.jsm',
|
||||||
'PlacesDBUtils.jsm',
|
'PlacesDBUtils.jsm',
|
||||||
|
|
|
@ -167,20 +167,21 @@ function* check_autocomplete(test) {
|
||||||
do_print("onSearchBegin received");
|
do_print("onSearchBegin received");
|
||||||
numSearchesStarted++;
|
numSearchesStarted++;
|
||||||
};
|
};
|
||||||
let deferred = Promise.defer();
|
let searchCompletePromise = new Promise(resolve => {
|
||||||
input.onSearchComplete = () => {
|
input.onSearchComplete = () => {
|
||||||
do_print("onSearchComplete received");
|
do_print("onSearchComplete received");
|
||||||
deferred.resolve();
|
resolve();
|
||||||
}
|
}
|
||||||
|
});
|
||||||
let expectedSearches = 1;
|
let expectedSearches = 1;
|
||||||
if (test.incompleteSearch) {
|
if (test.incompleteSearch) {
|
||||||
controller.startSearch(test.incompleteSearch);
|
controller.startSearch(test.incompleteSearch);
|
||||||
expectedSearches++;
|
expectedSearches++;
|
||||||
}
|
}
|
||||||
|
|
||||||
do_print("Searching for: '" + test.search + "'");
|
do_print("Searching for: '" + test.search + "'");
|
||||||
controller.startSearch(test.search);
|
controller.startSearch(test.search);
|
||||||
yield deferred.promise;
|
yield searchCompletePromise;
|
||||||
|
|
||||||
Assert.equal(numSearchesStarted, expectedSearches, "All searches started");
|
Assert.equal(numSearchesStarted, expectedSearches, "All searches started");
|
||||||
|
|
||||||
|
@ -415,6 +416,22 @@ function makeSwitchToTabMatch(url, extra = {}) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function makeExtensionMatch(extra = {}) {
|
||||||
|
let style = [ "action", "extension" ];
|
||||||
|
if (extra.heuristic) {
|
||||||
|
style.push("heuristic");
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
uri: makeActionURI("extension", {
|
||||||
|
content: extra.content,
|
||||||
|
keyword: extra.keyword,
|
||||||
|
}),
|
||||||
|
title: extra.description,
|
||||||
|
style,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
function setFaviconForHref(href, iconHref) {
|
function setFaviconForHref(href, iconHref) {
|
||||||
return new Promise(resolve => {
|
return new Promise(resolve => {
|
||||||
PlacesUtils.favicons.setAndFetchFaviconForPage(
|
PlacesUtils.favicons.setAndFetchFaviconForPage(
|
||||||
|
|
|
@ -0,0 +1,384 @@
|
||||||
|
/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*-
|
||||||
|
* vim:set ts=2 sw=2 sts=2 et:
|
||||||
|
* 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/. */
|
||||||
|
|
||||||
|
Cu.import("resource://gre/modules/ExtensionSearchHandler.jsm");
|
||||||
|
|
||||||
|
let controller = Cc["@mozilla.org/autocomplete/controller;1"].getService(Ci.nsIAutoCompleteController);
|
||||||
|
|
||||||
|
add_task(function* test_correct_errors_are_thrown() {
|
||||||
|
let keyword = "foo";
|
||||||
|
let anotherKeyword = "bar";
|
||||||
|
let unregisteredKeyword = "baz";
|
||||||
|
|
||||||
|
// Register a keyword.
|
||||||
|
ExtensionSearchHandler.registerKeyword(keyword, { emit: () => {} });
|
||||||
|
|
||||||
|
// Try registering the keyword again.
|
||||||
|
Assert.throws(() => ExtensionSearchHandler.registerKeyword(keyword, { emit: () => {} }));
|
||||||
|
|
||||||
|
// Register a different keyword.
|
||||||
|
ExtensionSearchHandler.registerKeyword(anotherKeyword, { emit: () => {} });
|
||||||
|
|
||||||
|
// Try calling handleSearch for an unregistered keyword.
|
||||||
|
Assert.throws(() => ExtensionSearchHandler.handleSearch(unregisteredKeyword, `${unregisteredKeyword} `, () => {}));
|
||||||
|
|
||||||
|
// Try calling handleSearch without a callback.
|
||||||
|
Assert.throws(() => ExtensionSearchHandler.handleSearch(unregisteredKeyword, `${unregisteredKeyword} `));
|
||||||
|
|
||||||
|
// Try getting the description for a keyword which isn't registered.
|
||||||
|
Assert.throws(() => ExtensionSearchHandler.getDescription(unregisteredKeyword));
|
||||||
|
|
||||||
|
// Try getting the extension name for a keyword which isn't registered.
|
||||||
|
Assert.throws(() => ExtensionSearchHandler.getExtensionName(unregisteredKeyword));
|
||||||
|
|
||||||
|
// Try setting the default suggestion for a keyword which isn't registered.
|
||||||
|
Assert.throws(() => ExtensionSearchHandler.setDefaultSuggestion(unregisteredKeyword, "suggestion"));
|
||||||
|
|
||||||
|
// Try calling handleInputCancelled when there is no active input session.
|
||||||
|
Assert.throws(() => ExtensionSearchHandler.handleInputCancelled());
|
||||||
|
|
||||||
|
// Try calling handleInputEntered when there is no active input session.
|
||||||
|
Assert.throws(() => ExtensionSearchHandler.handleInputEntered(anotherKeyword, `${anotherKeyword} test`, "tab"));
|
||||||
|
|
||||||
|
// Start a session by calling handleSearch with the registered keyword.
|
||||||
|
ExtensionSearchHandler.handleSearch(keyword, `${keyword} test`, () => {});
|
||||||
|
|
||||||
|
// Try providing suggestions for an unregistered keyword.
|
||||||
|
Assert.throws(() => ExtensionSearchHandler.addSuggestions(unregisteredKeyword, 0, []));
|
||||||
|
|
||||||
|
// Try providing suggestions for an inactive keyword.
|
||||||
|
Assert.throws(() => ExtensionSearchHandler.addSuggestions(anotherKeyword, 0, []));
|
||||||
|
|
||||||
|
// Try calling handleSearch for an inactive keyword.
|
||||||
|
Assert.throws(() => ExtensionSearchHandler.handleSearch(anotherKeyword, `${anotherKeyword} `, () => {}));
|
||||||
|
|
||||||
|
// Try calling addSuggestions with an old callback ID.
|
||||||
|
Assert.throws(() => ExtensionSearchHandler.addSuggestions(keyword, 0, []));
|
||||||
|
|
||||||
|
// Add suggestions with a valid callback ID.
|
||||||
|
ExtensionSearchHandler.addSuggestions(keyword, 1, []);
|
||||||
|
|
||||||
|
// Add suggestions again with a valid callback ID.
|
||||||
|
ExtensionSearchHandler.addSuggestions(keyword, 1, []);
|
||||||
|
|
||||||
|
// Try calling addSuggestions with a future callback ID.
|
||||||
|
Assert.throws(() => ExtensionSearchHandler.addSuggestions(keyword, 2, []));
|
||||||
|
|
||||||
|
// End the input session by calling handleInputCancelled.
|
||||||
|
ExtensionSearchHandler.handleInputCancelled();
|
||||||
|
|
||||||
|
// Try calling handleInputCancelled after the session has ended.
|
||||||
|
Assert.throws(() => ExtensionSearchHandler.handleInputCancelled());
|
||||||
|
|
||||||
|
// Try calling handleSearch that doesn't have a space after the keyword.
|
||||||
|
Assert.throws(() => ExtensionSearchHandler.handleSearch(anotherKeyword, `${anotherKeyword}`, () => {}));
|
||||||
|
|
||||||
|
// Try calling handleSearch with text starting with the wrong keyword.
|
||||||
|
Assert.throws(() => ExtensionSearchHandler.handleSearch(anotherKeyword, `${keyword} test`, () => {}));
|
||||||
|
|
||||||
|
// Start a new session by calling handleSearch with a different keyword
|
||||||
|
ExtensionSearchHandler.handleSearch(anotherKeyword, `${anotherKeyword} test`, () => {});
|
||||||
|
|
||||||
|
// Try adding suggestions again with the same callback ID now that the input session has ended.
|
||||||
|
Assert.throws(() => ExtensionSearchHandler.addSuggestions(keyword, 1, []));
|
||||||
|
|
||||||
|
// Add suggestions with a valid callback ID.
|
||||||
|
ExtensionSearchHandler.addSuggestions(anotherKeyword, 2, []);
|
||||||
|
|
||||||
|
// Try adding suggestions with a valid callback ID but a different keyword.
|
||||||
|
Assert.throws(() => ExtensionSearchHandler.addSuggestions(keyword, 2, []));
|
||||||
|
|
||||||
|
// Try adding suggestions with a valid callback ID but an unregistered keyword.
|
||||||
|
Assert.throws(() => ExtensionSearchHandler.addSuggestions(unregisteredKeyword, 2, []));
|
||||||
|
|
||||||
|
// Set the default suggestion.
|
||||||
|
ExtensionSearchHandler.setDefaultSuggestion(anotherKeyword, {description: "test result"});
|
||||||
|
|
||||||
|
// Try ending the session using handleInputEntered with a different keyword.
|
||||||
|
Assert.throws(() => ExtensionSearchHandler.handleInputEntered(keyword, `${keyword} test`, "tab"));
|
||||||
|
|
||||||
|
// Try calling handleInputEntered with invalid text.
|
||||||
|
Assert.throws(() => ExtensionSearchHandler.handleInputEntered(anotherKeyword, ` test`, "tab"));
|
||||||
|
|
||||||
|
// Try calling handleInputEntered with an invalid disposition.
|
||||||
|
Assert.throws(() => ExtensionSearchHandler.handleInputEntered(anotherKeyword, `${anotherKeyword} test`, "invalid"));
|
||||||
|
|
||||||
|
// End the session by calling handleInputEntered.
|
||||||
|
ExtensionSearchHandler.handleInputEntered(anotherKeyword, `${anotherKeyword} test`, "tab");
|
||||||
|
|
||||||
|
// Try calling handleInputEntered after the session has ended.
|
||||||
|
Assert.throws(() => ExtensionSearchHandler.handleInputEntered(anotherKeyword, `${anotherKeyword} test`, "tab"));
|
||||||
|
|
||||||
|
// Unregister the keyword.
|
||||||
|
ExtensionSearchHandler.unregisterKeyword(keyword);
|
||||||
|
|
||||||
|
// Try setting the default suggestion for the unregistered keyword.
|
||||||
|
Assert.throws(() => ExtensionSearchHandler.setDefaultSuggestion(keyword, {description: "test"}));
|
||||||
|
|
||||||
|
// Try handling a search with the unregistered keyword.
|
||||||
|
Assert.throws(() => ExtensionSearchHandler.handleSearch(keyword, `${keyword} test`, () => {}));
|
||||||
|
|
||||||
|
// Try unregistering the keyword again.
|
||||||
|
Assert.throws(() => ExtensionSearchHandler.unregisterKeyword(keyword));
|
||||||
|
|
||||||
|
// Unregister the other keyword.
|
||||||
|
ExtensionSearchHandler.unregisterKeyword(anotherKeyword);
|
||||||
|
|
||||||
|
// Try unregistering the word which was never registered.
|
||||||
|
Assert.throws(() => ExtensionSearchHandler.unregisterKeyword(unregisteredKeyword));
|
||||||
|
|
||||||
|
// Try setting the default suggestion for a word that was never registered.
|
||||||
|
Assert.throws(() => ExtensionSearchHandler.setDefaultSuggestion(unregisteredKeyword, {description: "test"}));
|
||||||
|
|
||||||
|
yield cleanup();
|
||||||
|
});
|
||||||
|
|
||||||
|
add_task(function* test_correct_events_are_emitted() {
|
||||||
|
let events = [];
|
||||||
|
function checkEvents(expectedEvents) {
|
||||||
|
Assert.equal(events.length, expectedEvents.length, "The correct number of events fired");
|
||||||
|
expectedEvents.forEach((e, i) => Assert.equal(e, events[i], `Expected "${e}" event to fire`));
|
||||||
|
events = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
let mockExtension = { emit: message => events.push(message) };
|
||||||
|
|
||||||
|
let keyword = "foo";
|
||||||
|
let anotherKeyword = "bar";
|
||||||
|
|
||||||
|
ExtensionSearchHandler.registerKeyword(keyword, mockExtension);
|
||||||
|
ExtensionSearchHandler.registerKeyword(anotherKeyword, mockExtension);
|
||||||
|
|
||||||
|
ExtensionSearchHandler.handleSearch(keyword, `${keyword} `, () => {});
|
||||||
|
checkEvents([ExtensionSearchHandler.MSG_INPUT_STARTED]);
|
||||||
|
|
||||||
|
ExtensionSearchHandler.handleSearch(keyword, `${keyword} f`, () => {});
|
||||||
|
checkEvents([ExtensionSearchHandler.MSG_INPUT_CHANGED]);
|
||||||
|
|
||||||
|
ExtensionSearchHandler.handleInputEntered(keyword, `${keyword} f`, "tab");
|
||||||
|
checkEvents([ExtensionSearchHandler.MSG_INPUT_ENTERED]);
|
||||||
|
|
||||||
|
ExtensionSearchHandler.handleSearch(keyword, `${keyword} f`, () => {});
|
||||||
|
checkEvents([
|
||||||
|
ExtensionSearchHandler.MSG_INPUT_STARTED,
|
||||||
|
ExtensionSearchHandler.MSG_INPUT_CHANGED
|
||||||
|
]);
|
||||||
|
|
||||||
|
ExtensionSearchHandler.handleInputCancelled();
|
||||||
|
checkEvents([ExtensionSearchHandler.MSG_INPUT_CANCELLED]);
|
||||||
|
|
||||||
|
ExtensionSearchHandler.handleSearch(anotherKeyword, `${anotherKeyword} baz`, () => {});
|
||||||
|
checkEvents([
|
||||||
|
ExtensionSearchHandler.MSG_INPUT_STARTED,
|
||||||
|
ExtensionSearchHandler.MSG_INPUT_CHANGED
|
||||||
|
]);
|
||||||
|
|
||||||
|
ExtensionSearchHandler.handleInputEntered(anotherKeyword, `${anotherKeyword} baz`, "tab");
|
||||||
|
checkEvents([ExtensionSearchHandler.MSG_INPUT_ENTERED]);
|
||||||
|
|
||||||
|
ExtensionSearchHandler.unregisterKeyword(keyword);
|
||||||
|
});
|
||||||
|
|
||||||
|
add_task(function* test_removes_suggestion_if_its_content_is_typed_in() {
|
||||||
|
let keyword = "test";
|
||||||
|
let extensionName = "Foo Bar";
|
||||||
|
|
||||||
|
let mockExtension = {
|
||||||
|
name: extensionName,
|
||||||
|
emit(message, text, id) {
|
||||||
|
if (message === ExtensionSearchHandler.MSG_INPUT_CHANGED) {
|
||||||
|
ExtensionSearchHandler.addSuggestions(keyword, id, [
|
||||||
|
{content: "foo", description: "first suggestion"},
|
||||||
|
{content: "bar", description: "second suggestion"},
|
||||||
|
{content: "baz", description: "third suggestion"},
|
||||||
|
]);
|
||||||
|
controller.stopSearch();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
ExtensionSearchHandler.registerKeyword(keyword, mockExtension);
|
||||||
|
|
||||||
|
yield check_autocomplete({
|
||||||
|
search: `${keyword} unmatched`,
|
||||||
|
searchParam: "enable-actions",
|
||||||
|
matches: [
|
||||||
|
makeExtensionMatch({heuristic: true, keyword, description: extensionName, content: `${keyword} unmatched`}),
|
||||||
|
makeExtensionMatch({keyword, content: `${keyword} foo`, description: "first suggestion"}),
|
||||||
|
makeExtensionMatch({keyword, content: `${keyword} bar`, description: "second suggestion"}),
|
||||||
|
makeExtensionMatch({keyword, content: `${keyword} baz`, description: "third suggestion"})
|
||||||
|
]
|
||||||
|
});
|
||||||
|
|
||||||
|
yield check_autocomplete({
|
||||||
|
search: `${keyword} foo`,
|
||||||
|
searchParam: "enable-actions",
|
||||||
|
matches: [
|
||||||
|
makeExtensionMatch({heuristic: true, keyword, description: extensionName, content: `${keyword} foo`}),
|
||||||
|
makeExtensionMatch({keyword, content: `${keyword} bar`, description: "second suggestion"}),
|
||||||
|
makeExtensionMatch({keyword, content: `${keyword} baz`, description: "third suggestion"})
|
||||||
|
]
|
||||||
|
});
|
||||||
|
|
||||||
|
yield check_autocomplete({
|
||||||
|
search: `${keyword} bar`,
|
||||||
|
searchParam: "enable-actions",
|
||||||
|
matches: [
|
||||||
|
makeExtensionMatch({heuristic: true, keyword, description: extensionName, content: `${keyword} bar`}),
|
||||||
|
makeExtensionMatch({keyword, content: `${keyword} foo`, description: "first suggestion"}),
|
||||||
|
makeExtensionMatch({keyword, content: `${keyword} baz`, description: "third suggestion"})
|
||||||
|
]
|
||||||
|
});
|
||||||
|
|
||||||
|
yield check_autocomplete({
|
||||||
|
search: `${keyword} baz`,
|
||||||
|
searchParam: "enable-actions",
|
||||||
|
matches: [
|
||||||
|
makeExtensionMatch({heuristic: true, keyword, description: extensionName, content: `${keyword} baz`}),
|
||||||
|
makeExtensionMatch({keyword, content: `${keyword} foo`, description: "first suggestion"}),
|
||||||
|
makeExtensionMatch({keyword, content: `${keyword} bar`, description: "second suggestion"})
|
||||||
|
]
|
||||||
|
});
|
||||||
|
|
||||||
|
ExtensionSearchHandler.unregisterKeyword(keyword);
|
||||||
|
yield cleanup();
|
||||||
|
});
|
||||||
|
|
||||||
|
add_task(function* test_extension_results_should_come_first() {
|
||||||
|
let keyword = "test";
|
||||||
|
let extensionName = "Omnibox Example";
|
||||||
|
|
||||||
|
let uri = NetUtil.newURI(`http://a.com/b`);
|
||||||
|
yield PlacesTestUtils.addVisits([
|
||||||
|
{ uri, title: `${keyword} -` },
|
||||||
|
]);
|
||||||
|
|
||||||
|
let mockExtension = {
|
||||||
|
name: extensionName,
|
||||||
|
emit(message, text, id) {
|
||||||
|
if (message === ExtensionSearchHandler.MSG_INPUT_CHANGED) {
|
||||||
|
ExtensionSearchHandler.addSuggestions(keyword, id, [
|
||||||
|
{content: "foo", description: "first suggestion"},
|
||||||
|
{content: "bar", description: "second suggestion"},
|
||||||
|
{content: "baz", description: "third suggestion"},
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
controller.stopSearch();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
ExtensionSearchHandler.registerKeyword(keyword, mockExtension);
|
||||||
|
|
||||||
|
// Start an input session before testing MSG_INPUT_CHANGED.
|
||||||
|
ExtensionSearchHandler.handleSearch(keyword, `${keyword} `, () => {});
|
||||||
|
|
||||||
|
yield check_autocomplete({
|
||||||
|
search: `${keyword} -`,
|
||||||
|
searchParam: "enable-actions",
|
||||||
|
matches: [
|
||||||
|
makeExtensionMatch({heuristic: true, keyword, description: extensionName, content: `${keyword} -`}),
|
||||||
|
makeExtensionMatch({keyword, content: `${keyword} foo`, description: "first suggestion"}),
|
||||||
|
makeExtensionMatch({keyword, content: `${keyword} bar`, description: "second suggestion"}),
|
||||||
|
makeExtensionMatch({keyword, content: `${keyword} baz`, description: "third suggestion"}),
|
||||||
|
{ uri, title: `${keyword} -` }
|
||||||
|
]
|
||||||
|
});
|
||||||
|
|
||||||
|
ExtensionSearchHandler.unregisterKeyword(keyword);
|
||||||
|
yield cleanup();
|
||||||
|
});
|
||||||
|
|
||||||
|
add_task(function* test_setting_the_default_suggestion() {
|
||||||
|
let keyword = "test";
|
||||||
|
let extensionName = "Omnibox Example";
|
||||||
|
|
||||||
|
let mockExtension = {
|
||||||
|
name: extensionName,
|
||||||
|
emit(message, text, id) {
|
||||||
|
if (message === ExtensionSearchHandler.MSG_INPUT_CHANGED) {
|
||||||
|
ExtensionSearchHandler.addSuggestions(keyword, id, []);
|
||||||
|
}
|
||||||
|
controller.stopSearch();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
ExtensionSearchHandler.registerKeyword(keyword, mockExtension);
|
||||||
|
|
||||||
|
ExtensionSearchHandler.setDefaultSuggestion(keyword, {
|
||||||
|
description: "hello world"
|
||||||
|
});
|
||||||
|
|
||||||
|
let searchString = `${keyword} search query`;
|
||||||
|
yield check_autocomplete({
|
||||||
|
search: searchString,
|
||||||
|
searchParam: "enable-actions",
|
||||||
|
matches: [
|
||||||
|
makeExtensionMatch({heuristic: true, keyword, description: "hello world", content: searchString}),
|
||||||
|
]
|
||||||
|
});
|
||||||
|
|
||||||
|
ExtensionSearchHandler.setDefaultSuggestion(keyword, {
|
||||||
|
description: "foo bar"
|
||||||
|
});
|
||||||
|
|
||||||
|
yield check_autocomplete({
|
||||||
|
search: searchString,
|
||||||
|
searchParam: "enable-actions",
|
||||||
|
matches: [
|
||||||
|
makeExtensionMatch({heuristic: true, keyword, description: "foo bar", content: searchString}),
|
||||||
|
]
|
||||||
|
});
|
||||||
|
|
||||||
|
ExtensionSearchHandler.unregisterKeyword(keyword);
|
||||||
|
yield cleanup();
|
||||||
|
});
|
||||||
|
|
||||||
|
add_task(function* test_maximum_number_of_suggestions_is_enforced() {
|
||||||
|
let keyword = "test";
|
||||||
|
let extensionName = "Omnibox Example";
|
||||||
|
|
||||||
|
let mockExtension = {
|
||||||
|
name: extensionName,
|
||||||
|
emit(message, text, id) {
|
||||||
|
if (message === ExtensionSearchHandler.MSG_INPUT_CHANGED) {
|
||||||
|
ExtensionSearchHandler.addSuggestions(keyword, id, [
|
||||||
|
{content: "a", description: "first suggestion"},
|
||||||
|
{content: "b", description: "second suggestion"},
|
||||||
|
{content: "c", description: "third suggestion"},
|
||||||
|
{content: "d", description: "fourth suggestion"},
|
||||||
|
{content: "e", description: "fifth suggestion"},
|
||||||
|
{content: "f", description: "sixth suggestion"},
|
||||||
|
{content: "g", description: "seventh suggestion"},
|
||||||
|
{content: "h", description: "eigth suggestion"},
|
||||||
|
{content: "i", description: "ninth suggestion"},
|
||||||
|
{content: "j", description: "tenth suggestion"},
|
||||||
|
]);
|
||||||
|
controller.stopSearch();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
ExtensionSearchHandler.registerKeyword(keyword, mockExtension);
|
||||||
|
|
||||||
|
// Start an input session before testing MSG_INPUT_CHANGED.
|
||||||
|
ExtensionSearchHandler.handleSearch(keyword, `${keyword} `, () => {});
|
||||||
|
|
||||||
|
yield check_autocomplete({
|
||||||
|
search: `${keyword} #`,
|
||||||
|
searchParam: "enable-actions",
|
||||||
|
matches: [
|
||||||
|
makeExtensionMatch({heuristic: true, keyword, description: extensionName, content: `${keyword} #`}),
|
||||||
|
makeExtensionMatch({keyword, content: `${keyword} a`, description: "first suggestion"}),
|
||||||
|
makeExtensionMatch({keyword, content: `${keyword} b`, description: "second suggestion"}),
|
||||||
|
makeExtensionMatch({keyword, content: `${keyword} c`, description: "third suggestion"}),
|
||||||
|
makeExtensionMatch({keyword, content: `${keyword} d`, description: "fourth suggestion"}),
|
||||||
|
makeExtensionMatch({keyword, content: `${keyword} e`, description: "fifth suggestion"}),
|
||||||
|
]
|
||||||
|
});
|
||||||
|
|
||||||
|
ExtensionSearchHandler.unregisterKeyword(keyword);
|
||||||
|
yield cleanup();
|
||||||
|
});
|
|
@ -24,6 +24,7 @@ support-files =
|
||||||
[test_empty_search.js]
|
[test_empty_search.js]
|
||||||
[test_enabled.js]
|
[test_enabled.js]
|
||||||
[test_escape_self.js]
|
[test_escape_self.js]
|
||||||
|
[test_extension_matches.js]
|
||||||
[test_ignore_protocol.js]
|
[test_ignore_protocol.js]
|
||||||
[test_keyword_search.js]
|
[test_keyword_search.js]
|
||||||
[test_keyword_search_actions.js]
|
[test_keyword_search_actions.js]
|
||||||
|
|
|
@ -2127,6 +2127,10 @@ extends="chrome://global/content/bindings/popup.xml#popup">
|
||||||
titleLooksLikeUrl = true;
|
titleLooksLikeUrl = true;
|
||||||
let visitStr = this._stringBundle.GetStringFromName("visit");
|
let visitStr = this._stringBundle.GetStringFromName("visit");
|
||||||
this._setUpDescription(this._actionText, visitStr, true);
|
this._setUpDescription(this._actionText, visitStr, true);
|
||||||
|
} else if (action.type == "extension") {
|
||||||
|
let content = action.params.content;
|
||||||
|
displayUrl = content;
|
||||||
|
this._setUpDescription(this._actionText, content, true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -3497,11 +3497,18 @@ KeyboardLayout::NotifyIdleServiceOfUserActivity()
|
||||||
sIdleService->ResetIdleTimeOut(0);
|
sIdleService->ResetIdleTimeOut(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
KeyboardLayout::KeyboardLayout() :
|
KeyboardLayout::KeyboardLayout()
|
||||||
mKeyboardLayout(0), mIsOverridden(false),
|
: mKeyboardLayout(0)
|
||||||
mIsPendingToRestoreKeyboardLayout(false)
|
, mIsOverridden(false)
|
||||||
|
, mIsPendingToRestoreKeyboardLayout(false)
|
||||||
{
|
{
|
||||||
mDeadKeyTableListHead = nullptr;
|
mDeadKeyTableListHead = nullptr;
|
||||||
|
// A dead key sequence should be made from up to 5 keys. Therefore, 4 is
|
||||||
|
// enough and makes sense because the item is uint8_t.
|
||||||
|
// (Although, even if it's possible to be 6 keys or more in a sequence,
|
||||||
|
// this array will be re-allocated).
|
||||||
|
mActiveDeadKeys.SetCapacity(4);
|
||||||
|
mDeadKeyShiftStates.SetCapacity(4);
|
||||||
|
|
||||||
// NOTE: LoadLayout() should be called via OnLayoutChange().
|
// NOTE: LoadLayout() should be called via OnLayoutChange().
|
||||||
}
|
}
|
||||||
|
@ -3613,8 +3620,7 @@ KeyboardLayout::InitNativeKey(NativeKey& aNativeKey,
|
||||||
// If it's in dead key sequence and dead char is inputted as is, we need to
|
// If it's in dead key sequence and dead char is inputted as is, we need to
|
||||||
// set the previous modifier state which is stored when preceding dead key
|
// set the previous modifier state which is stored when preceding dead key
|
||||||
// is pressed.
|
// is pressed.
|
||||||
UniCharsAndModifiers deadChars =
|
UniCharsAndModifiers deadChars = GetDeadUniCharsAndModifiers();
|
||||||
GetUniCharsAndModifiers(mActiveDeadKey, mDeadKeyShiftState);
|
|
||||||
aNativeKey.mCommittedCharsAndModifiers.
|
aNativeKey.mCommittedCharsAndModifiers.
|
||||||
OverwriteModifiersIfBeginsWith(deadChars);
|
OverwriteModifiersIfBeginsWith(deadChars);
|
||||||
// Finish the dead key sequence.
|
// Finish the dead key sequence.
|
||||||
|
@ -3622,6 +3628,12 @@ KeyboardLayout::InitNativeKey(NativeKey& aNativeKey,
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// If it's a dead key, aNativeKey will be initialized by
|
||||||
|
// MaybeInitNativeKeyAsDeadKey().
|
||||||
|
if (MaybeInitNativeKeyAsDeadKey(aNativeKey, aModKeyState)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// If the key is not a usual printable key, KeyboardLayout class assume that
|
// If the key is not a usual printable key, KeyboardLayout class assume that
|
||||||
// it's not cause dead char nor printable char. Therefore, there are nothing
|
// it's not cause dead char nor printable char. Therefore, there are nothing
|
||||||
// to do here fore such keys (e.g., function keys).
|
// to do here fore such keys (e.g., function keys).
|
||||||
|
@ -3636,12 +3648,6 @@ KeyboardLayout::InitNativeKey(NativeKey& aNativeKey,
|
||||||
MOZ_ASSERT(aNativeKey.mKeyNameIndex == KEY_NAME_INDEX_USE_STRING,
|
MOZ_ASSERT(aNativeKey.mKeyNameIndex == KEY_NAME_INDEX_USE_STRING,
|
||||||
"Printable key's key name index must be KEY_NAME_INDEX_USE_STRING");
|
"Printable key's key name index must be KEY_NAME_INDEX_USE_STRING");
|
||||||
|
|
||||||
// If it's a dead key, aNativeKey will be initialized by
|
|
||||||
// MaybeInitNativeKeyAsDeadKey().
|
|
||||||
if (MaybeInitNativeKeyAsDeadKey(aNativeKey, aModKeyState)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// If it's in dead key handling and the pressed key causes a composite
|
// If it's in dead key handling and the pressed key causes a composite
|
||||||
// character, aNativeKey will be initialized by
|
// character, aNativeKey will be initialized by
|
||||||
// MaybeInitNativeKeyWithCompositeChar().
|
// MaybeInitNativeKeyWithCompositeChar().
|
||||||
|
@ -3659,19 +3665,10 @@ KeyboardLayout::InitNativeKey(NativeKey& aNativeKey,
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Although, this shouldn't occur, if active dead key isn't a printable
|
|
||||||
// key, we cannot handle it because KeyboardLayout assumes that dead key
|
|
||||||
// is never mapped to non-printable keys (e.g., F4, etc). Please be aware,
|
|
||||||
// it's possible, but we've not known such special keyboard layout yet.
|
|
||||||
if (NS_WARN_IF(!IsPrintableCharKey(mActiveDeadKey))) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// If the key doesn't cause a composite character with preceding dead key,
|
// If the key doesn't cause a composite character with preceding dead key,
|
||||||
// initialize aNativeKey with the dead-key character followed by current
|
// initialize aNativeKey with the dead-key character followed by current
|
||||||
// key's character.
|
// key's character.
|
||||||
UniCharsAndModifiers deadChars =
|
UniCharsAndModifiers deadChars = GetDeadUniCharsAndModifiers();
|
||||||
GetUniCharsAndModifiers(mActiveDeadKey, mDeadKeyShiftState);
|
|
||||||
aNativeKey.mCommittedCharsAndModifiers = deadChars + baseChars;
|
aNativeKey.mCommittedCharsAndModifiers = deadChars + baseChars;
|
||||||
if (aNativeKey.IsKeyDownMessage()) {
|
if (aNativeKey.IsKeyDownMessage()) {
|
||||||
DeactivateDeadKeyState();
|
DeactivateDeadKeyState();
|
||||||
|
@ -3683,25 +3680,28 @@ KeyboardLayout::MaybeInitNativeKeyAsDeadKey(
|
||||||
NativeKey& aNativeKey,
|
NativeKey& aNativeKey,
|
||||||
const ModifierKeyState& aModKeyState)
|
const ModifierKeyState& aModKeyState)
|
||||||
{
|
{
|
||||||
if (!IsDeadKey(aNativeKey.mOriginalVirtualKeyCode, aModKeyState)) {
|
// Only when it's not in dead key sequence, we can trust IsDeadKey() result.
|
||||||
|
if (!IsInDeadKeySequence() &&
|
||||||
|
!IsDeadKey(aNativeKey.mOriginalVirtualKeyCode, aModKeyState)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// If it's a keydown event but not in dead key sequence or it's a keyup
|
// When keydown message is followed by a dead char message, it should be
|
||||||
// event of a dead key which activated current dead key sequence,
|
// initialized as dead key.
|
||||||
// initialize aNativeKey as a dead key event.
|
bool isDeadKeyDownEvent =
|
||||||
if ((aNativeKey.IsKeyDownMessage() && !IsInDeadKeySequence()) ||
|
aNativeKey.IsKeyDownMessage() &&
|
||||||
(!aNativeKey.IsKeyDownMessage() &&
|
aNativeKey.IsFollowedByDeadCharMessage();
|
||||||
mActiveDeadKey == aNativeKey.mOriginalVirtualKeyCode)) {
|
|
||||||
|
// When keyup message is received, let's check if it's one of preceding
|
||||||
|
// dead keys because keydown message order and keyup message order may be
|
||||||
|
// different.
|
||||||
|
bool isDeadKeyUpEvent =
|
||||||
|
!aNativeKey.IsKeyDownMessage() &&
|
||||||
|
mActiveDeadKeys.Contains(aNativeKey.mOriginalVirtualKeyCode);
|
||||||
|
|
||||||
|
if (isDeadKeyDownEvent || isDeadKeyUpEvent) {
|
||||||
ActivateDeadKeyState(aNativeKey, aModKeyState);
|
ActivateDeadKeyState(aNativeKey, aModKeyState);
|
||||||
#ifdef DEBUG
|
// Any dead key events don't generate characters. So, a dead key should
|
||||||
UniCharsAndModifiers deadChars =
|
|
||||||
GetNativeUniCharsAndModifiers(aNativeKey.mOriginalVirtualKeyCode,
|
|
||||||
aModKeyState);
|
|
||||||
MOZ_ASSERT(deadChars.Length() == 1,
|
|
||||||
"dead key must generate only one character");
|
|
||||||
#endif
|
|
||||||
// First dead key event doesn't generate characters. Dead key should
|
|
||||||
// cause only keydown event and keyup event whose KeyboardEvent.key
|
// cause only keydown event and keyup event whose KeyboardEvent.key
|
||||||
// values are "Dead".
|
// values are "Dead".
|
||||||
aNativeKey.mCommittedCharsAndModifiers.Clear();
|
aNativeKey.mCommittedCharsAndModifiers.Clear();
|
||||||
|
@ -3720,27 +3720,18 @@ KeyboardLayout::MaybeInitNativeKeyAsDeadKey(
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// When non-printable key event comes during a dead key sequence, that must
|
||||||
|
// be a modifier key event. So, such events shouldn't be handled as a part
|
||||||
|
// of the dead key sequence.
|
||||||
|
if (!IsDeadKey(aNativeKey.mOriginalVirtualKeyCode, aModKeyState)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
// FYI: Following code may run when the user doesn't input text actually
|
// FYI: Following code may run when the user doesn't input text actually
|
||||||
// but the key sequence is a dead key sequence. For example,
|
// but the key sequence is a dead key sequence. For example,
|
||||||
// ` -> Ctrl+` with Spanish keyboard layout. Let's keep using this
|
// ` -> Ctrl+` with Spanish keyboard layout. Let's keep using this
|
||||||
// complicated code for now because this runs really rarely.
|
// complicated code for now because this runs really rarely.
|
||||||
|
|
||||||
if (NS_WARN_IF(!IsPrintableCharKey(mActiveDeadKey))) {
|
|
||||||
#if defined(DEBUG) || defined(MOZ_CRASHREPORTER)
|
|
||||||
nsPrintfCString warning("The virtual key index (%d) of mActiveDeadKey "
|
|
||||||
"(0x%02X) is not a printable key "
|
|
||||||
"(aNativeKey.mOriginalVirtualKeyCode=0x%02X)",
|
|
||||||
GetKeyIndex(mActiveDeadKey), mActiveDeadKey,
|
|
||||||
aNativeKey.mOriginalVirtualKeyCode);
|
|
||||||
NS_WARNING(warning.get());
|
|
||||||
#ifdef MOZ_CRASHREPORTER
|
|
||||||
CrashReporter::AppendAppNotesToCrashReport(
|
|
||||||
NS_LITERAL_CSTRING("\n") + warning);
|
|
||||||
#endif // #ifdef MOZ_CRASHREPORTER
|
|
||||||
#endif // #if defined(DEBUG) || defined(MOZ_CRASHREPORTER)
|
|
||||||
MOZ_CRASH("Trying to reference out of range of mVirtualKeys");
|
|
||||||
}
|
|
||||||
|
|
||||||
// Dead key followed by another dead key may cause a composed character
|
// Dead key followed by another dead key may cause a composed character
|
||||||
// (e.g., "Russian - Mnemonic" keyboard layout's 's' -> 'c').
|
// (e.g., "Russian - Mnemonic" keyboard layout's 's' -> 'c').
|
||||||
if (MaybeInitNativeKeyWithCompositeChar(aNativeKey, aModKeyState)) {
|
if (MaybeInitNativeKeyWithCompositeChar(aNativeKey, aModKeyState)) {
|
||||||
|
@ -3749,8 +3740,7 @@ KeyboardLayout::MaybeInitNativeKeyAsDeadKey(
|
||||||
|
|
||||||
// Otherwise, dead key followed by another dead key causes inputting both
|
// Otherwise, dead key followed by another dead key causes inputting both
|
||||||
// character.
|
// character.
|
||||||
UniCharsAndModifiers prevDeadChars =
|
UniCharsAndModifiers prevDeadChars = GetDeadUniCharsAndModifiers();
|
||||||
GetUniCharsAndModifiers(mActiveDeadKey, mDeadKeyShiftState);
|
|
||||||
UniCharsAndModifiers newChars =
|
UniCharsAndModifiers newChars =
|
||||||
GetUniCharsAndModifiers(aNativeKey.mOriginalVirtualKeyCode, aModKeyState);
|
GetUniCharsAndModifiers(aNativeKey.mOriginalVirtualKeyCode, aModKeyState);
|
||||||
// But keypress events should be fired for each committed character.
|
// But keypress events should be fired for each committed character.
|
||||||
|
@ -3770,8 +3760,7 @@ KeyboardLayout::MaybeInitNativeKeyWithCompositeChar(
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (NS_WARN_IF(!IsPrintableCharKey(mActiveDeadKey)) ||
|
if (NS_WARN_IF(!IsPrintableCharKey(aNativeKey.mOriginalVirtualKeyCode))) {
|
||||||
NS_WARN_IF(!IsPrintableCharKey(aNativeKey.mOriginalVirtualKeyCode))) {
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -3781,8 +3770,7 @@ KeyboardLayout::MaybeInitNativeKeyWithCompositeChar(
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
char16_t compositeChar =
|
char16_t compositeChar = GetCompositeChar(baseChars.CharAt(0));
|
||||||
GetCompositeChar(mActiveDeadKey, mDeadKeyShiftState, baseChars.CharAt(0));
|
|
||||||
if (!compositeChar) {
|
if (!compositeChar) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
@ -3824,16 +3812,43 @@ KeyboardLayout::GetNativeUniCharsAndModifiers(
|
||||||
return mVirtualKeys[key].GetNativeUniChars(shiftState);
|
return mVirtualKeys[key].GetNativeUniChars(shiftState);
|
||||||
}
|
}
|
||||||
|
|
||||||
char16_t
|
UniCharsAndModifiers
|
||||||
KeyboardLayout::GetCompositeChar(uint8_t aVirtualKeyOfDeadKey,
|
KeyboardLayout::GetDeadUniCharsAndModifiers() const
|
||||||
VirtualKey::ShiftState aShiftStateOfDeadKey,
|
|
||||||
char16_t aBaseChar) const
|
|
||||||
{
|
{
|
||||||
int32_t key = GetKeyIndex(aVirtualKeyOfDeadKey);
|
MOZ_RELEASE_ASSERT(mActiveDeadKeys.Length() == mDeadKeyShiftStates.Length());
|
||||||
|
|
||||||
|
if (NS_WARN_IF(mActiveDeadKeys.IsEmpty())) {
|
||||||
|
return UniCharsAndModifiers();
|
||||||
|
}
|
||||||
|
|
||||||
|
UniCharsAndModifiers result;
|
||||||
|
for (size_t i = 0; i < mActiveDeadKeys.Length(); ++i) {
|
||||||
|
result +=
|
||||||
|
GetUniCharsAndModifiers(mActiveDeadKeys[i], mDeadKeyShiftStates[i]);
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
char16_t
|
||||||
|
KeyboardLayout::GetCompositeChar(char16_t aBaseChar) const
|
||||||
|
{
|
||||||
|
if (NS_WARN_IF(mActiveDeadKeys.IsEmpty())) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
// XXX Currently, we don't support computing a composite character with
|
||||||
|
// two or more dead keys since it needs big table for supporting
|
||||||
|
// long chained dead keys. However, this should be a minor bug
|
||||||
|
// because this runs only when the latest keydown event does not cause
|
||||||
|
// WM_(SYS)CHAR messages. So, when user wants to input a character,
|
||||||
|
// this path never runs.
|
||||||
|
if (mActiveDeadKeys.Length() > 1) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
int32_t key = GetKeyIndex(mActiveDeadKeys[0]);
|
||||||
if (key < 0) {
|
if (key < 0) {
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
return mVirtualKeys[key].GetCompositeChar(aShiftStateOfDeadKey, aBaseChar);
|
return mVirtualKeys[key].GetCompositeChar(mDeadKeyShiftStates[0], aBaseChar);
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
|
@ -3860,7 +3875,8 @@ KeyboardLayout::LoadLayout(HKL aLayout)
|
||||||
// characters.
|
// characters.
|
||||||
uint16_t shiftStatesWithBaseChars = 0;
|
uint16_t shiftStatesWithBaseChars = 0;
|
||||||
|
|
||||||
mActiveDeadKey = -1;
|
mActiveDeadKeys.Clear();
|
||||||
|
mDeadKeyShiftStates.Clear();
|
||||||
|
|
||||||
ReleaseDeadKeyTables();
|
ReleaseDeadKeyTables();
|
||||||
|
|
||||||
|
@ -4099,26 +4115,26 @@ KeyboardLayout::ActivateDeadKeyState(const NativeKey& aNativeKey,
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
MOZ_RELEASE_ASSERT(IsPrintableCharKey(aNativeKey.mOriginalVirtualKeyCode));
|
mActiveDeadKeys.AppendElement(aNativeKey.mOriginalVirtualKeyCode);
|
||||||
|
mDeadKeyShiftStates.AppendElement(
|
||||||
mActiveDeadKey = aNativeKey.mOriginalVirtualKeyCode;
|
VirtualKey::ModifierKeyStateToShiftState(aModKeyState));
|
||||||
mDeadKeyShiftState = VirtualKey::ModifierKeyStateToShiftState(aModKeyState);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
KeyboardLayout::DeactivateDeadKeyState()
|
KeyboardLayout::DeactivateDeadKeyState()
|
||||||
{
|
{
|
||||||
if (mActiveDeadKey < 0) {
|
if (mActiveDeadKeys.IsEmpty()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
BYTE kbdState[256];
|
BYTE kbdState[256];
|
||||||
memset(kbdState, 0, sizeof(kbdState));
|
memset(kbdState, 0, sizeof(kbdState));
|
||||||
|
|
||||||
VirtualKey::FillKbdState(kbdState, mDeadKeyShiftState);
|
// Assume that the last dead key can finish dead key sequence.
|
||||||
|
VirtualKey::FillKbdState(kbdState, mDeadKeyShiftStates.LastElement());
|
||||||
EnsureDeadKeyActive(false, mActiveDeadKey, kbdState);
|
EnsureDeadKeyActive(false, mActiveDeadKeys.LastElement(), kbdState);
|
||||||
mActiveDeadKey = -1;
|
mActiveDeadKeys.Clear();
|
||||||
|
mDeadKeyShiftStates.Clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
bool
|
bool
|
||||||
|
|
|
@ -694,7 +694,7 @@ public:
|
||||||
* It starts when a dead key is down and ends when another key down causes
|
* It starts when a dead key is down and ends when another key down causes
|
||||||
* inactivating the dead key state.
|
* inactivating the dead key state.
|
||||||
*/
|
*/
|
||||||
bool IsInDeadKeySequence() const { return mActiveDeadKey >= 0; }
|
bool IsInDeadKeySequence() const { return !mActiveDeadKeys.IsEmpty(); }
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* IsSysKey() returns true if aVirtualKey with aModKeyState causes WM_SYSKEY*
|
* IsSysKey() returns true if aVirtualKey with aModKeyState causes WM_SYSKEY*
|
||||||
|
@ -811,8 +811,14 @@ private:
|
||||||
|
|
||||||
VirtualKey mVirtualKeys[NS_NUM_OF_KEYS];
|
VirtualKey mVirtualKeys[NS_NUM_OF_KEYS];
|
||||||
DeadKeyTableListEntry* mDeadKeyTableListHead;
|
DeadKeyTableListEntry* mDeadKeyTableListHead;
|
||||||
int32_t mActiveDeadKey; // -1 = no active dead-key
|
// When mActiveDeadKeys is empty, it's not in dead key sequence.
|
||||||
VirtualKey::ShiftState mDeadKeyShiftState;
|
// Otherwise, it contains virtual keycodes which are pressed in current
|
||||||
|
// dead key sequence.
|
||||||
|
nsTArray<uint8_t> mActiveDeadKeys;
|
||||||
|
// mDeadKeyShiftStates is always same length as mActiveDeadKeys.
|
||||||
|
// This stores shift states at pressing each dead key stored in
|
||||||
|
// mActiveDeadKeys.
|
||||||
|
nsTArray<VirtualKey::ShiftState> mDeadKeyShiftStates;
|
||||||
|
|
||||||
bool mIsOverridden;
|
bool mIsOverridden;
|
||||||
bool mIsPendingToRestoreKeyboardLayout;
|
bool mIsPendingToRestoreKeyboardLayout;
|
||||||
|
@ -883,16 +889,20 @@ private:
|
||||||
uint8_t aVirtualKey,
|
uint8_t aVirtualKey,
|
||||||
VirtualKey::ShiftState aShiftState) const;
|
VirtualKey::ShiftState aShiftState) const;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* GetDeadUniCharsAndModifiers() returns dead chars which are stored in
|
||||||
|
* current dead key sequence. So, this is stateful.
|
||||||
|
*/
|
||||||
|
UniCharsAndModifiers GetDeadUniCharsAndModifiers() const;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* GetCompositeChar() returns a composite character with dead character
|
* GetCompositeChar() returns a composite character with dead character
|
||||||
* caused by aVirtualKeyOfDeadKey and aShiftStateOfDeadKey and a base
|
* caused by mActiveDeadKeys, mDeadKeyShiftStates and a base character
|
||||||
* character (aBaseChar).
|
* (aBaseChar).
|
||||||
* If the combination of the dead character and the base character doesn't
|
* If the combination of the dead character and the base character doesn't
|
||||||
* cause a composite character, this returns 0.
|
* cause a composite character, this returns 0.
|
||||||
*/
|
*/
|
||||||
char16_t GetCompositeChar(uint8_t aVirtualKeyOfDeadKey,
|
char16_t GetCompositeChar(char16_t aBaseChar) const;
|
||||||
VirtualKey::ShiftState aShiftStateOfDeadKey,
|
|
||||||
char16_t aBaseChar) const;
|
|
||||||
|
|
||||||
// NativeKey class should access InitNativeKey() directly, but it shouldn't
|
// NativeKey class should access InitNativeKey() directly, but it shouldn't
|
||||||
// be available outside of NativeKey. So, let's make NativeKey a friend
|
// be available outside of NativeKey. So, let's make NativeKey a friend
|
||||||
|
|
Загрузка…
Ссылка в новой задаче