Bug 1440189 - Stop dispatching keypress event to the default event group in web content (only Nightly and early Beta) unless web page isn't in blacklist r=smaug

UI Events declares that keypress event should be fired only when the keydown
sequence produces some characters.  For conforming to UI Events and
compatibility with the other browsers, we should stop dispatching keypress
events for non-printable keys.

For getting regression reports, we should enable this new behavior only
on Nightly.

However, some web apps actually broken with the standardized behavior.  For
protecting testers from known broken web apps, this patch introduces a
blacklist to take the traditional behavior under specific domain (and path in
it, optionally).  Currently, docs.google.com and mail.google.com are set by
default.

MozReview-Commit-ID: HSrYX8LUB0p

--HG--
extra : rebase_source : a2677d07410af289534db051767543a25c9a957a
This commit is contained in:
Masayuki Nakano 2018-03-23 12:06:55 +09:00
Родитель 821aa618ed
Коммит c389fb3752
4 изменённых файлов: 143 добавлений и 1 удалений

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

@ -829,6 +829,10 @@ PresShell::PresShell()
, mIsLastChromeOnlyEscapeKeyConsumed(false)
, mHasReceivedPaintMessage(false)
, mHasHandledUserInput(false)
#ifdef NIGHTLY_BUILD
, mForceDispatchKeyPressEventsForNonPrintableKeys(false)
, mInitializedForceDispatchKeyPressEventsForNonPrintableKeys(false)
#endif // #ifdef NIGHTLY_BUILD
{
MOZ_LOG(gLog, LogLevel::Debug, ("PresShell::PresShell this=%p", this));
@ -7812,7 +7816,103 @@ PresShell::HandleEventInternal(WidgetEvent* aEvent,
return rv;
}
#ifdef NIGHTLY_BUILD
static already_AddRefed<nsIURI>
GetDocumentURIToCompareWithBlacklist(PresShell& aPresShell)
{
nsPresContext* presContext = aPresShell.GetPresContext();
if (NS_WARN_IF(!presContext)) {
return nullptr;
}
// If the document is sandboxed document or data: document, we should
// get URI of the parent document.
for (nsIDocument* document = presContext->Document();
document && document->IsContentDocument();
document = document->GetParentDocument()) {
// The document URI may be about:blank even if it comes from actual web
// site. Therefore, we need to check the URI of its principal.
nsIPrincipal* principal = document->NodePrincipal();
if (principal->GetIsNullPrincipal()) {
continue;
}
nsCOMPtr<nsIURI> uri;
principal->GetURI(getter_AddRefs(uri));
return uri.forget();
}
return nullptr;
}
static bool
DispatchKeyPressEventsEvenForNonPrintableKeys(nsIURI* aURI)
{
if (!aURI) {
return false;
}
nsAutoCString scheme;
aURI->GetScheme(scheme);
if (!scheme.EqualsLiteral("http") &&
!scheme.EqualsLiteral("https")) {
return false;
}
nsAutoCString host;
aURI->GetHost(host);
if (host.IsEmpty()) {
return false;
}
// The black list is comma separated domain list. Each item may start with
// "*.". If starts with "*.", it matches any sub-domains.
static const char* kPrefNameOfBlackList =
"dom.keyboardevent.keypress.hack.dispatch_non_printable_keys";
nsAutoCString blackList;
Preferences::GetCString(kPrefNameOfBlackList, blackList);
if (blackList.IsEmpty()) {
return false;
}
for (;;) {
int32_t index = blackList.Find(host, false);
if (index >= 0 &&
static_cast<uint32_t>(index) + host.Length() <= blackList.Length() &&
// If start of the black list or next to ","?
(!index || blackList[index - 1] == ',')) {
// If end of the black list or immediately before ","?
size_t indexAfterHost = index + host.Length();
if (indexAfterHost == blackList.Length() ||
blackList[indexAfterHost] == ',') {
return true;
}
// If next character is '/', we need to check the path too.
// We assume the path in blacklist means "/foo" + "*".
if (blackList[indexAfterHost] == '/') {
int32_t endOfPath = blackList.Find(",", false, indexAfterHost);
nsDependentCSubstring::size_type length =
endOfPath < 0 ? static_cast<nsDependentCSubstring::size_type>(-1) :
endOfPath - indexAfterHost;
nsDependentCSubstring pathInBlackList(blackList,
indexAfterHost, length);
nsAutoCString filePath;
aURI->GetFilePath(filePath);
if (StringBeginsWith(filePath, pathInBlackList)) {
return true;
}
}
}
int32_t startIndexOfCurrentLevel = host[0] == '*' ? 1 : 0;
int32_t startIndexOfNextLevel =
host.Find(".", false, startIndexOfCurrentLevel + 1);
if (startIndexOfNextLevel <= 0) {
return false;
}
host = NS_LITERAL_CSTRING("*") +
nsDependentCSubstring(host, startIndexOfNextLevel);
}
return false;
}
#endif // #ifdef NIGHTLY_BUILD
nsresult
PresShell::DispatchEventToDOM(WidgetEvent* aEvent,
@ -7840,6 +7940,26 @@ PresShell::DispatchEventToDOM(WidgetEvent* aEvent,
if (eventTarget) {
if (aEvent->IsBlockedForFingerprintingResistance()) {
aEvent->mFlags.mOnlySystemGroupDispatchInContent = true;
#ifdef NIGHTLY_BUILD
} else if (aEvent->mMessage == eKeyPress &&
aEvent->mFlags.mOnlySystemGroupDispatchInContent) {
// If eKeyPress event is marked as not dispatched in the default event
// group in web content, it's caused by non-printable key or key
// combination. In this case, UI Events declares that browsers
// shouldn't dispatch keypress event. However, some web apps may be
// broken with this strict behavior due to historical issue.
// Therefore, we need to keep dispatching keypress event for such keys
// even with breaking the standard.
if (!mInitializedForceDispatchKeyPressEventsForNonPrintableKeys) {
mInitializedForceDispatchKeyPressEventsForNonPrintableKeys = true;
nsCOMPtr<nsIURI> uri = GetDocumentURIToCompareWithBlacklist(*this);
mForceDispatchKeyPressEventsForNonPrintableKeys =
DispatchKeyPressEventsEvenForNonPrintableKeys(uri);
}
if (mForceDispatchKeyPressEventsForNonPrintableKeys) {
aEvent->mFlags.mOnlySystemGroupDispatchInContent = false;
}
#endif // #ifdef NIGHTLY_BUILD
}
if (aEvent->mClass == eCompositionEventClass) {

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

@ -872,6 +872,14 @@ private:
// Whether we have ever handled a user input event
bool mHasHandledUserInput : 1;
#ifdef NIGHTLY_BUILD
// Whether we should dispatch keypress events even for non-printable keys
// for keeping backward compatibility.
bool mForceDispatchKeyPressEventsForNonPrintableKeys : 1;
// Whether mForceDispatchKeyPressEventsForNonPrintableKeys is initialized.
bool mInitializedForceDispatchKeyPressEventsForNonPrintableKeys : 1;
#endif // #ifdef NIGHTLY_BUILD
static bool sDisableNonTestMouseEvents;
TimeStamp mLastOSWake;

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

@ -227,7 +227,19 @@ pref("dom.keyboardevent.dispatch_during_composition", false);
// If this is true, TextEventDispatcher dispatches keypress event with setting
// WidgetEvent::mFlags::mOnlySystemGroupDispatchInContent to true if it won't
// cause inputting printable character.
#ifdef NIGHTLY_BUILD
pref("dom.keyboardevent.keypress.dispatch_non_printable_keys_only_system_group_in_content", true);
// Blacklist of domains of web apps which are not aware of strict keypress
// dispatching behavior. This is comma separated list. If you need to match
// all sub-domains, you can specify it as "*.example.com". Additionally, you
// can limit the path. E.g., "example.com/foo" means "example.com/foo*". So,
// if you need to limit under a directory, the path should end with "/" like
// "example.com/foo/". Note that this cannot limit port number for now.
pref("dom.keyboardevent.keypress.hack.dispatch_non_printable_keys",
"docs.google.com,mail.google.com");
#else
pref("dom.keyboardevent.keypress.dispatch_non_printable_keys_only_system_group_in_content", false);
#endif
// Whether the WebMIDI API is enabled
pref("dom.webmidi.enabled", false);

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

@ -45,7 +45,7 @@ TextEventDispatcher::TextEventDispatcher(nsIWidget* aWidget)
&sDispatchKeyPressEventsOnlySystemGroupInContent,
"dom.keyboardevent.keypress."
"dispatch_non_printable_keys_only_system_group_in_content",
false);
true);
sInitialized = true;
}
@ -697,6 +697,8 @@ TextEventDispatcher::DispatchKeyboardEventInternal(
if (sDispatchKeyPressEventsOnlySystemGroupInContent &&
keyEvent.mMessage == eKeyPress &&
!keyEvent.ShouldKeyPressEventBeFiredOnContent()) {
// Note that even if we set it to true, this may be overwritten by
// PresShell::DispatchEventToDOM().
keyEvent.mFlags.mOnlySystemGroupDispatchInContent = true;
}