Bug 1514940 - part 1: Forcibly disable new keyCode/charCode value of keypress events if the document is Confluence r=smaug,Ehsan,kmag
Old Confluence does not aware of conflated model keypress event (see UI Events spec, https://w3c.github.io/uievents/#determine-keypress-keyCode). Additionally, Confluence can be hosted with any domains. Therefore, we cannot use blacklist to disable the conflated model keypress event only on it. This patch checks whether current or parent document is Confluence with JS module, called KeyPressEventModelCheckerChild. For kicking this module, nsHTMLDocument dispatches an custom event, CheckKeyPressEventModel, when it becomes editable only first time. Finally, if it's a Confluence instance, the module let PresShell know that we need to use split model keypress event in it. Differential Revision: https://phabricator.services.mozilla.com/D17907
This commit is contained in:
@@ -150,6 +150,8 @@ support-files = bug1017086_inner.html
|
||||
[test_bug1264380.html]
|
||||
[test_bug1327798.html]
|
||||
subsuite = clipboard
|
||||
[test_bug1514940.html]
|
||||
skip-if = !debug
|
||||
[test_click_on_reframed_generated_text.html]
|
||||
[test_clickevent_on_input.html]
|
||||
skip-if = toolkit == 'android' #CRASH_DUMP, RANDOM
|
||||
|
||||
90
dom/events/test/test_bug1514940.html
Normal file
90
dom/events/test/test_bug1514940.html
Normal file
@@ -0,0 +1,90 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<!--
|
||||
https://bugzilla.mozilla.org/show_bug.cgi?id=1514940
|
||||
-->
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>Testing whether "keypress" event model is forcibly split model if the document is old Confluence instance</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=1514940">Bug 1514940</a>
|
||||
<p id="display"></p>
|
||||
<pre id="test"></pre>
|
||||
<input id="input">
|
||||
<iframe id="iframe" srcdoc="<html><body><p>Here is editor</p></body></html>"></iframe>
|
||||
<script>
|
||||
// Emulate window.tinyMCE.CursorTargetPlugin().getInfo() which is referred by
|
||||
// KeyPresEventModelCheckerChild.
|
||||
class CursorTargetPluginImpl {
|
||||
getInfo() {
|
||||
return {
|
||||
longname: "Cursor Target plugin",
|
||||
author: "Atlassian",
|
||||
authorurl: "http://www.atlassian.com",
|
||||
version: "1.0",
|
||||
};
|
||||
}
|
||||
}
|
||||
var tinyMCE = {
|
||||
plugins: {
|
||||
CursorTargetPlugin: CursorTargetPluginImpl,
|
||||
},
|
||||
};
|
||||
|
||||
SimpleTest.waitForExplicitFinish();
|
||||
SimpleTest.waitForFocus(async function doTests() {
|
||||
await SpecialPowers.pushPrefEnv({
|
||||
set: [
|
||||
["dom.keyboardevent.keypress.set_keycode_and_charcode_to_same_value", true],
|
||||
],
|
||||
});
|
||||
|
||||
let iframe = document.getElementById("iframe");
|
||||
let waitForCheckKeyPressEventModelEvent = new Promise(resolve => {
|
||||
SpecialPowers.addSystemEventListener(iframe.contentDocument, "CheckKeyPressEventModel", () => {
|
||||
resolve();
|
||||
}, false);
|
||||
});
|
||||
iframe.contentDocument.body.setAttribute("contenteditable", "true");
|
||||
await waitForCheckKeyPressEventModelEvent;
|
||||
iframe.contentDocument.body.focus();
|
||||
let keypressEvent;
|
||||
iframe.contentDocument.body.addEventListener("keypress", aEvent => keypressEvent = aEvent, {once: true});
|
||||
synthesizeKey("a", {}, iframe.contentWindow);
|
||||
is(keypressEvent.keyCode, 0,
|
||||
"keyCode value of 'a' should be 0");
|
||||
is(keypressEvent.charCode, "a".charCodeAt(0),
|
||||
"charCode value of 'a' should be 'a'");
|
||||
|
||||
iframe.contentDocument.body.addEventListener("keypress", aEvent => keypressEvent = aEvent, {once: true});
|
||||
synthesizeKey("KEY_Enter", {}, iframe.contentWindow);
|
||||
is(keypressEvent.keyCode, KeyboardEvent.DOM_VK_RETURN,
|
||||
"keyCode value of 'Enter' should be DOM_VK_RETURN");
|
||||
is(keypressEvent.charCode, 0,
|
||||
"charCode value of 'Enter' should be 0");
|
||||
|
||||
let input = document.getElementById("input");
|
||||
input.focus();
|
||||
input.addEventListener("keypress", aEvent => keypressEvent = aEvent, {once: true});
|
||||
synthesizeKey("a", {});
|
||||
is(keypressEvent.keyCode, "a".charCodeAt(0),
|
||||
"keyCode value of 'a' in the parent document should be 'a'");
|
||||
is(keypressEvent.charCode, "a".charCodeAt(0),
|
||||
"charCode value of 'a' in the parent document should be 'a'");
|
||||
|
||||
input.addEventListener("keypress", aEvent => keypressEvent = aEvent, {once: true});
|
||||
synthesizeKey("KEY_Enter");
|
||||
is(keypressEvent.keyCode, KeyboardEvent.DOM_VK_RETURN,
|
||||
"keyCode value of 'Enter' in the parent document should be DOM_VK_RETURN");
|
||||
is(keypressEvent.charCode, KeyboardEvent.DOM_VK_RETURN,
|
||||
"charCode value of 'Enter' in the parent document should be DOM_VK_RETURN");
|
||||
|
||||
SimpleTest.finish();
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -176,7 +176,8 @@ nsHTMLDocument::nsHTMLDocument()
|
||||
mContentEditableCount(0),
|
||||
mEditingState(EditingState::eOff),
|
||||
mDisableCookieAccess(false),
|
||||
mPendingMaybeEditingStateChanged(false) {
|
||||
mPendingMaybeEditingStateChanged(false),
|
||||
mHasBeenEditable(false) {
|
||||
mType = eHTML;
|
||||
mDefaultElementType = kNameSpaceID_XHTML;
|
||||
mCompatMode = eCompatibility_NavQuirks;
|
||||
@@ -2434,9 +2435,45 @@ nsresult nsHTMLDocument::EditingStateChanged() {
|
||||
}
|
||||
htmlEditor->SyncRealTimeSpell();
|
||||
|
||||
MaybeDispatchCheckKeyPressEventModelEvent();
|
||||
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
void nsHTMLDocument::MaybeDispatchCheckKeyPressEventModelEvent() {
|
||||
// Currently, we need to check only when we're becoming editable for
|
||||
// contenteditable.
|
||||
if (mEditingState != eContentEditable) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (mHasBeenEditable) {
|
||||
return;
|
||||
}
|
||||
mHasBeenEditable = true;
|
||||
|
||||
// Dispatch "CheckKeyPressEventModel" event. That is handled only by
|
||||
// KeyPressEventModelCheckerChild. Then, it calls SetKeyPressEventModel()
|
||||
// with proper keypress event for the active web app.
|
||||
WidgetEvent checkEvent(true, eUnidentifiedEvent);
|
||||
checkEvent.mSpecifiedEventType = nsGkAtoms::onCheckKeyPressEventModel;
|
||||
checkEvent.mFlags.mCancelable = false;
|
||||
checkEvent.mFlags.mBubbles = false;
|
||||
checkEvent.mFlags.mOnlySystemGroupDispatch = true;
|
||||
// Post the event rather than dispatching it synchronously because we need
|
||||
// a call of SetKeyPressEventModel() before first key input. Therefore, we
|
||||
// can avoid paying unnecessary runtime cost for most web apps.
|
||||
(new AsyncEventDispatcher(this, checkEvent))->PostDOMEvent();
|
||||
}
|
||||
|
||||
void nsHTMLDocument::SetKeyPressEventModel(uint16_t aKeyPressEventModel) {
|
||||
nsIPresShell* presShell = GetShell();
|
||||
if (!presShell) {
|
||||
return;
|
||||
}
|
||||
presShell->SetKeyPressEventModel(aKeyPressEventModel);
|
||||
}
|
||||
|
||||
void nsHTMLDocument::SetDesignMode(const nsAString& aDesignMode,
|
||||
nsIPrincipal& aSubjectPrincipal,
|
||||
ErrorResult& rv) {
|
||||
|
||||
@@ -213,6 +213,8 @@ class nsHTMLDocument : public mozilla::dom::Document, public nsIHTMLDocument {
|
||||
|
||||
void UserInteractionForTesting();
|
||||
|
||||
void SetKeyPressEventModel(uint16_t aKeyPressEventModel);
|
||||
|
||||
protected:
|
||||
~nsHTMLDocument();
|
||||
|
||||
@@ -300,6 +302,15 @@ class nsHTMLDocument : public mozilla::dom::Document, public nsIHTMLDocument {
|
||||
virtual void SetDocumentCharacterSet(
|
||||
NotNull<const Encoding*> aEncoding) override;
|
||||
|
||||
/**
|
||||
* MaybeDispatchCheckKeyPressEventModelEvent() dispatches
|
||||
* "CheckKeyPressEventModel" event to check whether we should dispatch
|
||||
* keypress events in confluent model or split model. This should be
|
||||
* called only when mEditingState is changed to eDesignMode or
|
||||
* eConentEditable at first time.
|
||||
*/
|
||||
void MaybeDispatchCheckKeyPressEventModelEvent();
|
||||
|
||||
// Tracks if we are currently processing any document.write calls (either
|
||||
// implicit or explicit). Note that if a write call writes out something which
|
||||
// would block the parser, then mWriteLevel will be incorrect until the parser
|
||||
@@ -337,6 +348,10 @@ class nsHTMLDocument : public mozilla::dom::Document, public nsIHTMLDocument {
|
||||
* MaybeEditingStateChanged() script runners from a nested scope.
|
||||
*/
|
||||
bool mPendingMaybeEditingStateChanged;
|
||||
|
||||
// mHasBeenEditable is set to true when mEditingState is firstly set to
|
||||
// eDesignMode or eContentEditable.
|
||||
bool mHasBeenEditable;
|
||||
};
|
||||
|
||||
namespace mozilla {
|
||||
|
||||
@@ -77,4 +77,29 @@ partial interface HTMLDocument {
|
||||
|
||||
[ChromeOnly]
|
||||
void userInteractionForTesting();
|
||||
|
||||
/**
|
||||
* setKeyPressEventModel() is called when we need to check whether the web
|
||||
* app requires specific keypress event model or not.
|
||||
*
|
||||
* @param aKeyPressEventModel Proper keypress event model for the web app.
|
||||
* KEYPRESS_EVENT_MODEL_DEFAULT:
|
||||
* Use default keypress event model. I.e., depending on
|
||||
* "dom.keyboardevent.keypress.set_keycode_and_charcode_to_same_value"
|
||||
* pref.
|
||||
* KEYPRESS_EVENT_MODEL_SPLIT:
|
||||
* Use split model. I.e, if keypress event inputs a character,
|
||||
* keyCode should be 0. Otherwise, charCode should be 0.
|
||||
* KEYPRESS_EVENT_MODEL_CONFLATED:
|
||||
* Use conflated model. I.e., keyCode and charCode values of each
|
||||
* keypress event should be set to same value.
|
||||
*/
|
||||
[ChromeOnly]
|
||||
const unsigned short KEYPRESS_EVENT_MODEL_DEFAULT = 0;
|
||||
[ChromeOnly]
|
||||
const unsigned short KEYPRESS_EVENT_MODEL_SPLIT = 1;
|
||||
[ChromeOnly]
|
||||
const unsigned short KEYPRESS_EVENT_MODEL_CONFLATED = 2;
|
||||
[ChromeOnly]
|
||||
void setKeyPressEventModel(unsigned short aKeyPressEventModel);
|
||||
};
|
||||
|
||||
@@ -7811,7 +7811,7 @@ nsresult PresShell::EventHandler::DispatchEventToDOM(
|
||||
nsContentUtils::IsURIInPrefList(
|
||||
uri,
|
||||
"dom.keyboardevent.keypress.hack.dispatch_non_printable_keys");
|
||||
mPresShell->mForceUseLegacyKeyCodeAndCharCodeValues =
|
||||
mPresShell->mForceUseLegacyKeyCodeAndCharCodeValues |=
|
||||
nsContentUtils::IsURIInPrefList(uri,
|
||||
"dom.keyboardevent.keypress.hack."
|
||||
"use_legacy_keycode_and_charcode");
|
||||
|
||||
@@ -12,6 +12,7 @@
|
||||
#include "MobileViewportManager.h"
|
||||
#include "mozilla/Attributes.h"
|
||||
#include "mozilla/EventForwards.h"
|
||||
#include "mozilla/dom/HTMLDocumentBinding.h"
|
||||
#include "mozilla/layers/FocusTarget.h"
|
||||
#include "mozilla/MemoryReporting.h"
|
||||
#include "mozilla/ServoStyleSet.h"
|
||||
@@ -325,6 +326,12 @@ class PresShell final : public nsIPresShell,
|
||||
|
||||
void FireResizeEvent() override;
|
||||
|
||||
void SetKeyPressEventModel(uint16_t aKeyPressEventModel) override {
|
||||
mForceUseLegacyKeyCodeAndCharCodeValues |=
|
||||
aKeyPressEventModel ==
|
||||
dom::HTMLDocument_Binding::KEYPRESS_EVENT_MODEL_SPLIT;
|
||||
}
|
||||
|
||||
static PresShell* GetShellForEventTarget(nsIFrame* aFrame,
|
||||
nsIContent* aContent);
|
||||
static PresShell* GetShellForTouchEvent(WidgetGUIEvent* aEvent);
|
||||
|
||||
@@ -1616,6 +1616,12 @@ class nsIPresShell : public nsStubDocumentObserver {
|
||||
|
||||
void NativeAnonymousContentRemoved(nsIContent* aAnonContent);
|
||||
|
||||
/**
|
||||
* See HTMLDocument.setKeyPressEventModel() in HTMLDocument.webidl for the
|
||||
* detail.
|
||||
*/
|
||||
virtual void SetKeyPressEventModel(uint16_t aKeyPressEventModel) = 0;
|
||||
|
||||
protected:
|
||||
/**
|
||||
* Refresh observer management.
|
||||
|
||||
76
toolkit/actors/KeyPressEventModelCheckerChild.jsm
Normal file
76
toolkit/actors/KeyPressEventModelCheckerChild.jsm
Normal file
@@ -0,0 +1,76 @@
|
||||
/* -*- mode: js; indent-tabs-mode: nil; js-indent-level: 2 -*- */
|
||||
/* vim: set ts=2 sw=2 sts=2 et tw=80: */
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
"use strict";
|
||||
|
||||
var EXPORTED_SYMBOLS = ["KeyPressEventModelCheckerChild"];
|
||||
|
||||
const {ActorChild} = ChromeUtils.import("resource://gre/modules/ActorChild.jsm");
|
||||
const {AppConstants} = ChromeUtils.import("resource://gre/modules/AppConstants.jsm");
|
||||
|
||||
class KeyPressEventModelCheckerChild extends ActorChild {
|
||||
// Currently, the event is dispatched only when the document becomes editable
|
||||
// because of contenteditable. If you need to add new editor which is in
|
||||
// designMode, you need to change MaybeDispatchCheckKeyPressEventModelEvent()
|
||||
// of nsHTMLDocument.
|
||||
handleEvent(aEvent) {
|
||||
if (!AppConstants.DEBUG) {
|
||||
// Stop propagation in opt build to save the propagation cost.
|
||||
// However, the event is necessary for running test_bug1514940.html.
|
||||
// Therefore, we need to keep propagating it at least on debug build.
|
||||
aEvent.stopImmediatePropagation();
|
||||
}
|
||||
|
||||
// Currently, even if we set HTMLDocument.KEYPRESS_EVENT_MODEL_CONFLATED
|
||||
// here, conflated model isn't used forcibly. If you need it, you need
|
||||
// to change WidgetKeyboardEvent, dom::KeyboardEvent and PresShell.
|
||||
let model = HTMLDocument.KEYPRESS_EVENT_MODEL_DEFAULT;
|
||||
if (this._isOldConfluence(aEvent.target.ownerGlobal)) {
|
||||
model = HTMLDocument.KEYPRESS_EVENT_MODEL_SPLIT;
|
||||
}
|
||||
aEvent.target.setKeyPressEventModel(model);
|
||||
}
|
||||
|
||||
_isOldConfluence(aWindow) {
|
||||
if (!aWindow) {
|
||||
return false;
|
||||
}
|
||||
// aWindow should be an editor window in <iframe>. However, we don't know
|
||||
// whether it can be without <iframe>. Anyway, there should be tinyMCE
|
||||
// object in the parent window or in the window.
|
||||
let tinyMCEObject;
|
||||
// First, try to retrieve tinyMCE object from parent window.
|
||||
try {
|
||||
tinyMCEObject = ChromeUtils.waiveXrays(aWindow.parent).tinyMCE;
|
||||
} catch (e) {
|
||||
// Ignore the exception for now.
|
||||
}
|
||||
// Next, if there is no tinyMCE object in the parent window, let's check
|
||||
// the window.
|
||||
if (!tinyMCEObject) {
|
||||
try {
|
||||
tinyMCEObject = ChromeUtils.waiveXrays(aWindow).tinyMCE;
|
||||
} catch (e) {
|
||||
// Fallthrough to return false below.
|
||||
}
|
||||
// If we couldn't find tinyMCE object, let's assume that it's not
|
||||
// Confluence instance.
|
||||
if (!tinyMCEObject) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
// If there is tinyMCE object, we can assume that we loaded Confluence
|
||||
// instance. So, let's check the version whether it allows conflated
|
||||
// keypress event model.
|
||||
try {
|
||||
let {author, version} =
|
||||
new tinyMCEObject.plugins.CursorTargetPlugin().getInfo();
|
||||
return author === "Atlassian" && version === "1.0";
|
||||
} catch (e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -10,6 +10,9 @@ with Files('**'):
|
||||
with Files('Finder*.jsm'):
|
||||
BUG_COMPONENT = ('Toolkit', 'Find Toolbar')
|
||||
|
||||
with Files('KeyPressEventModelCheckerChild.jsm'):
|
||||
BUG_COMPONENT = ('Core', 'DOM: Events')
|
||||
|
||||
TESTING_JS_MODULES += [
|
||||
'TestChild.jsm',
|
||||
'TestParent.jsm',
|
||||
@@ -24,6 +27,7 @@ FINAL_TARGET_FILES.actors += [
|
||||
'ExtFindChild.jsm',
|
||||
'FindBarChild.jsm',
|
||||
'FinderChild.jsm',
|
||||
'KeyPressEventModelCheckerChild.jsm',
|
||||
'PopupBlockingChild.jsm',
|
||||
'PrintingChild.jsm',
|
||||
'PurgeSessionHistoryChild.jsm',
|
||||
|
||||
@@ -185,6 +185,15 @@ let ACTORS = {
|
||||
},
|
||||
},
|
||||
|
||||
KeyPressEventModelChecker: {
|
||||
child: {
|
||||
module: "resource://gre/actors/KeyPressEventModelCheckerChild.jsm",
|
||||
events: {
|
||||
"CheckKeyPressEventModel": {capture: true, mozSystemGroup: true},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
ManifestMessages: {
|
||||
child: {
|
||||
module: "resource://gre/modules/ManifestMessagesChild.jsm",
|
||||
|
||||
@@ -747,6 +747,7 @@ STATIC_ATOMS = [
|
||||
Atom("onchargingtimechange", "onchargingtimechange"),
|
||||
Atom("onchecking", "onchecking"),
|
||||
Atom("onCheckboxStateChange", "onCheckboxStateChange"),
|
||||
Atom("onCheckKeyPressEventModel", "onCheckKeyPressEventModel"),
|
||||
Atom("onclick", "onclick"),
|
||||
Atom("onclose", "onclose"),
|
||||
Atom("oncommand", "oncommand"),
|
||||
|
||||
Reference in New Issue
Block a user