Bug 737100 - Extend Pointer Lock (Mouse Lock) for non-fullscreen elements, p=smaug,dolske, r=cpearce,dolske,smaug

This commit is contained in:
Olli Pettay 2013-03-24 12:32:44 +02:00
Родитель 3091b711ff
Коммит 0e8cf1f788
31 изменённых файлов: 568 добавлений и 201 удалений

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

@ -450,6 +450,13 @@
</popupnotificationcontent>
</popupnotification>
<popupnotification id="pointerLock-notification" hidden="true">
<popupnotificationcontent orient="vertical" align="start">
<separator class="thin"/>
<label id="pointerLock-cancel" value="&pointerLock.notification.message;"/>
</popupnotificationcontent>
</popupnotification>
<popupnotification id="mixed-content-blocked-notification" hidden="true">
<popupnotificationcontent orient="vertical" align="start">
<separator/>
@ -580,6 +587,7 @@
<image id="mixed-content-blocked-notification-icon" class="notification-anchor-icon" role="button"/>
<image id="webRTC-shareDevices-notification-icon" class="notification-anchor-icon" role="button"/>
<image id="webRTC-sharingDevices-notification-icon" class="notification-anchor-icon" role="button"/>
<image id="pointerLock-notification-icon" class="notification-anchor-icon" role="button"/>
</box>
<!-- Use onclick instead of normal popup= syntax since the popup
code fires onmousedown, and hence eats our favicon drag events.

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

@ -64,6 +64,8 @@
<command id="cmd_geoToggle" oncommand="onRadioClick('geo');"/>
<command id="cmd_indexedDBToggle" oncommand="onRadioClick('indexedDB');"/>
<command id="cmd_pluginsToggle" oncommand="onPluginRadioClick(event);"/>
<command id="cmd_pointerLockDef" oncommand="onCheckboxClick('pointerLock');"/>
<command id="cmd_pointerLockToggle" oncommand="onRadioClick('pointerLock');"/>
</commandset>
<keyset id="pageInfoKeySet">
@ -413,6 +415,18 @@
</radiogroup>
</hbox>
</vbox>
<vbox class="permission" id="permPointerLockRow" >
<label class="permissionLabel" id="permPointerLockLabel"
value="&permPointerLock;" control="pointerLockRadioGroup"/>
<hbox id="permPointerLockBox" role="group" aria-labelledby="permPointerLockLabel">
<checkbox id="pointerLockDef" command="cmd_pointerLockDef" label="&permAskAlways;"/>
<spacer flex="1"/>
<radiogroup id="pointerLockRadioGroup" orient="horizontal">
<radio id="pointerLock#1" command="cmd_pointerLockToggle" label="&permAllow;"/>
<radio id="pointerLock#2" command="cmd_pointerLockToggle" label="&permBlock;"/>
</radiogroup>
</hbox>
</vbox>
</vbox>
</vbox>

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

@ -64,7 +64,11 @@ var gPermObj = {
fullscreen: function getFullscreenDefaultPermissions()
{
return UNKNOWN;
}
},
pointerLock: function getPointerLockPermissions()
{
return BLOCK;
},
};
var permissionObserver = {
@ -129,9 +133,13 @@ function initRow(aPartId)
var checkbox = document.getElementById(aPartId + "Def");
var command = document.getElementById("cmd_" + aPartId + "Toggle");
// Geolocation permission consumers use testExactPermission, not testPermission.
var perm = aPartId == "geo" ? permissionManager.testExactPermission(gPermURI, aPartId) :
permissionManager.testPermission(gPermURI, aPartId);
// Geolocation and PointerLock permission consumers use testExactPermission, not testPermission.
var perm;
if (aPartId == "geo" || aPartId == "pointerLock")
perm = permissionManager.testExactPermission(gPermURI, aPartId);
else
perm = permissionManager.testPermission(gPermURI, aPartId);
if (perm) {
checkbox.checked = false;
command.removeAttribute("disabled");

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

@ -1640,9 +1640,14 @@ ContentPermissionPrompt.prototype = {
*/
_showPrompt: function CPP_showPrompt(aRequest, aMessage, aPermission, aActions,
aNotificationId, aAnchorId) {
function onFullScreen() {
popup.remove();
}
var browserBundle = Services.strings.createBundle("chrome://browser/locale/browser.properties");
var requestingWindow = aRequest.window.top;
var topDoc = requestingWindow.document;
var chromeWin = this._getChromeWindow(requestingWindow).wrappedJSObject;
var browser = chromeWin.gBrowser.getBrowserForDocument(requestingWindow.document);
var requestPrincipal = aRequest.principal;
@ -1685,10 +1690,34 @@ ContentPermissionPrompt.prototype = {
popupNotificationActions.push(action);
}
var mainAction = popupNotificationActions[0];
var mainAction = popupNotificationActions.length ?
popupNotificationActions[0] : null;
var secondaryActions = popupNotificationActions.splice(1);
chromeWin.PopupNotifications.show(browser, aNotificationId, aMessage, aAnchorId,
mainAction, secondaryActions);
var options = null;
if (aRequest.type == "pointerLock") {
// If there's no mainAction, this is the autoAllow warning prompt.
let autoAllow = !mainAction;
options = { removeOnDismissal: autoAllow,
eventCallback: function (type) {
if (type == "removed") {
topDoc.removeEventListener("mozfullscreenchange", onFullScreen);
if (autoAllow)
aRequest.allow();
}
},
};
}
var popup = chromeWin.PopupNotifications.show(browser, aNotificationId, aMessage, aAnchorId,
mainAction, secondaryActions, options);
if (aRequest.type == "pointerLock") {
// pointerLock is automatically allowed in fullscreen mode (and revoked
// upon exit), so if the page enters fullscreen mode after requesting
// pointerLock (but before the user has granted permission), we should
// remove the now-impotent notification.
topDoc.addEventListener("mozfullscreenchange", onFullScreen);
}
},
_promptGeo : function(aRequest) {
@ -1779,9 +1808,50 @@ ContentPermissionPrompt.prototype = {
"web-notifications-notification-icon");
},
_promptPointerLock: function CPP_promtPointerLock(aRequest, autoAllow) {
let browserBundle = Services.strings.createBundle("chrome://browser/locale/browser.properties");
let requestingURI = aRequest.principal.URI;
let originString = requestingURI.schemeIs("file") ? requestingURI.path : requestingURI.host;
let message = browserBundle.formatStringFromName(autoAllow ?
"pointerLock.autoLock.title" : "pointerLock.title",
[originString], 1);
// If this is an autoAllow info prompt, offer no actions.
// _showPrompt() will allow the request when it's dismissed.
let actions = [];
if (!autoAllow) {
actions = [
{
stringId: "pointerLock.allow",
action: null,
expireType: null,
callback: function() {},
},
{
stringId: "pointerLock.alwaysAllow",
action: Ci.nsIPermissionManager.ALLOW_ACTION,
expireType: null,
callback: function() {},
},
{
stringId: "pointerLock.neverAllow",
action: Ci.nsIPermissionManager.DENY_ACTION,
expireType: null,
callback: function() {},
},
];
}
this._showPrompt(aRequest, message, "pointerLock", actions, "pointerLock", "pointerLock-notification-icon");
},
prompt: function CPP_prompt(request) {
const kFeatureKeys = { "geolocation" : "geo",
"desktop-notification" : "desktop-notification" };
"desktop-notification" : "desktop-notification",
"pointerLock" : "pointerLock",
};
// Make sure that we support the request.
if (!(request.type in kFeatureKeys)) {
@ -1795,19 +1865,24 @@ ContentPermissionPrompt.prototype = {
if (!(requestingURI instanceof Ci.nsIStandardURL))
return;
var autoAllow = false;
var permissionKey = kFeatureKeys[request.type];
var result = Services.perms.testExactPermissionFromPrincipal(requestingPrincipal, permissionKey);
if (result == Ci.nsIPermissionManager.ALLOW_ACTION) {
request.allow();
return;
}
if (result == Ci.nsIPermissionManager.DENY_ACTION) {
request.cancel();
return;
}
if (result == Ci.nsIPermissionManager.ALLOW_ACTION) {
autoAllow = true;
// For pointerLock, we still want to show a warning prompt.
if (request.type != "pointerLock") {
request.allow();
return;
}
}
// Show the prompt.
switch (request.type) {
case "geolocation":
@ -1816,8 +1891,12 @@ ContentPermissionPrompt.prototype = {
case "desktop-notification":
this._promptWebNotifications(request);
break;
case "pointerLock":
this._promptPointerLock(request, autoAllow);
break;
}
}
},
};
var components = [BrowserGlue, ContentPermissionPrompt];

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

@ -647,3 +647,5 @@ just addresses the organization to follow, e.g. "This site is run by " -->
<!ENTITY mixedContentBlocked.helplink "Learn more">
<!ENTITY mixedContentBlocked.moreinfo "Most websites will still work properly even when this content is blocked.">
<!ENTITY pointerLock.notification.message "Press ESC at any time to show it again.">

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

@ -275,6 +275,17 @@ webNotifications.neverShow=Always Block Notifications
webNotifications.neverShow.accesskey=N
webNotifications.showFromSite=Would you like to show notifications from %S?
# Pointer lock UI
pointerLock.allow=Hide mouse cursor
pointerLock.allow.accesskey=H
pointerLock.alwaysAllow=Always allow hiding
pointerLock.alwaysAllow.accesskey=A
pointerLock.neverAllow=Never allow hiding
pointerLock.neverAllow.accesskey=N
pointerLock.title=Would you like to allow the mouse cursor to be hidden on %S?
pointerLock.autoLock.title=%S will hide the mouse cursor.
# Phishing/Malware Notification Bar.
# LOCALIZATION NOTE (notAForgery, notAnAttack)
# The two button strings will never be shown at the same time, so

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

@ -66,6 +66,7 @@
<!ENTITY permGeo "Share Location">
<!ENTITY permPlugins "Activate Plugins">
<!ENTITY permFullscreen "Enter Fullscreen">
<!ENTITY permPointerLock "Hide the Mouse Cursor">
<!ENTITY permIndexedDB "Maintain Offline Storage">
<!ENTITY permClearStorage "Clear Storage">

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

@ -1226,6 +1226,10 @@ toolbar[iconsize="small"] #webrtc-status-button {
list-style-image: url(chrome://browser/skin/webRTC-shareDevice-64.png);
}
.popup-notification-icon[popupid="pointerLock"] {
list-style-image: url(chrome://browser/skin/pointerLock-64.png);
}
/* Notification icon box */
#notification-popup-box {
position: relative;
@ -1333,6 +1337,13 @@ toolbar[iconsize="small"] #webrtc-status-button {
list-style-image: url(chrome://browser/skin/notification-16.png);
}
#pointerLock-notification-icon {
list-style-image: url(chrome://browser/skin/pointerLock-16.png);
}
#pointerLock-cancel {
margin: 0px;
}
#treecolAutoCompleteImage {
max-width : 36px;
}

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

@ -39,6 +39,8 @@ browser.jar:
* skin/classic/browser/pageInfo.css
skin/classic/browser/pageInfo.png
skin/classic/browser/page-livemarks.png
skin/classic/browser/pointerLock-16.png
skin/classic/browser/pointerLock-64.png
skin/classic/browser/Privacy-16.png
skin/classic/browser/Privacy-48.png
skin/classic/browser/privatebrowsing-mask.png

Двоичные данные
browser/themes/linux/pointerLock-16.png Normal file

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 249 B

Двоичные данные
browser/themes/linux/pointerLock-64.png Normal file

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 1.1 KiB

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

@ -3155,6 +3155,17 @@ toolbarbutton.chevron > .toolbarbutton-menu-dropmarker {
}
}
#pointerLock-notification-icon {
list-style-image: url(chrome://browser/skin/pointerLock-16.png);
}
@media (min-resolution: 2dppx) {
#pointerLock-notification-icon {
list-style-image: url(chrome://browser/skin/pointerLock-16@2x.png);
}
}
.popup-notification-icon {
width: 64px;
height: 64px;
@ -3248,6 +3259,19 @@ toolbarbutton.chevron > .toolbarbutton-menu-dropmarker {
}
}
.popup-notification-icon[popupid="pointerLock"] {
list-style-image: url(chrome://browser/skin/pointerLock-64.png);
}
@media (min-resolution: 2dppx) {
.popup-notification-icon[popupid="pointerLock"] {
list-style-image: url(chrome://browser/skin/pointerLock-64@2x.png);
}
}
#pointerLock-cancel {
margin: 0px;
}
#mixed-content-blocked-helplink {
margin: 0px;
}

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

@ -57,6 +57,10 @@ browser.jar:
skin/classic/browser/page-livemarks.png
skin/classic/browser/page-livemarks@2x.png
skin/classic/browser/pageInfo.css
skin/classic/browser/pointerLock-16.png
skin/classic/browser/pointerLock-16@2x.png
skin/classic/browser/pointerLock-64.png
skin/classic/browser/pointerLock-64@2x.png
skin/classic/browser/Privacy-16.png
skin/classic/browser/Privacy-48.png
skin/classic/browser/privatebrowsing-mask.png

Двоичные данные
browser/themes/osx/pointerLock-16.png Normal file

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 249 B

Двоичные данные
browser/themes/osx/pointerLock-16@2x.png Normal file

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 411 B

Двоичные данные
browser/themes/osx/pointerLock-64.png Normal file

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 1.1 KiB

Двоичные данные
browser/themes/osx/pointerLock-64@2x.png Normal file

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 2.1 KiB

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

@ -2283,6 +2283,10 @@ toolbarbutton.bookmark-item[dragover="true"][open="true"] {
list-style-image: url(chrome://browser/skin/webRTC-shareDevice-64.png);
}
.popup-notification-icon[popupid="pointerLock"] {
list-style-image: url(chrome://browser/skin/pointerLock-64.png);
}
/* Notification icon box */
#notification-popup-box {
position: relative;
@ -2388,6 +2392,13 @@ toolbarbutton.bookmark-item[dragover="true"][open="true"] {
list-style-image: url(chrome://browser/skin/notification-16.png);
}
#pointerLock-notification-icon {
list-style-image: url(chrome://browser/skin/pointerLock-16.png);
}
#pointerLock-cancel {
margin: 0px;
}
#identity-popup-container {
min-width: 280px;
}

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

@ -47,6 +47,8 @@ browser.jar:
skin/classic/browser/pageInfo.css
skin/classic/browser/pageInfo.png
skin/classic/browser/page-livemarks.png (feeds/feedIcon16.png)
skin/classic/browser/pointerLock-16.png
skin/classic/browser/pointerLock-64.png
skin/classic/browser/Privacy-16.png
skin/classic/browser/Privacy-48.png
skin/classic/browser/privatebrowsing-light.png
@ -280,6 +282,8 @@ browser.jar:
skin/classic/aero/browser/pageInfo.css
skin/classic/aero/browser/pageInfo.png (pageInfo-aero.png)
skin/classic/aero/browser/page-livemarks.png (feeds/feedIcon16-aero.png)
skin/classic/aero/browser/pointerLock-16.png (pointerLock-16.png)
skin/classic/aero/browser/pointerLock-64.png (pointerLock-64.png)
skin/classic/aero/browser/Privacy-16.png (Privacy-16-aero.png)
skin/classic/aero/browser/Privacy-48.png (Privacy-48-aero.png)
skin/classic/aero/browser/privatebrowsing-light.png

Двоичные данные
browser/themes/windows/pointerLock-16.png Normal file

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 249 B

Двоичные данные
browser/themes/windows/pointerLock-64.png Normal file

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 1.1 KiB

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

@ -1882,6 +1882,12 @@ public:
*/
static nsIDocument* GetFullscreenAncestor(nsIDocument* aDoc);
/**
* Returns true if aWin and the current pointer lock document
* have common scriptable top window.
*/
static bool IsInPointerLockContext(nsIDOMWindow* aWin);
/**
* Returns the time limit on handling user input before
* nsEventStateManager::IsHandlingUserInput() stops returning true.

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

@ -969,7 +969,7 @@ public:
virtual void RequestPointerLock(Element* aElement) = 0;
static void UnlockPointer();
static void UnlockPointer(nsIDocument* aDoc = nullptr);
//----------------------------------------------------------------------
@ -2021,7 +2021,7 @@ public:
Element* GetMozPointerLockElement();
void MozExitPointerLock()
{
UnlockPointer();
UnlockPointer(this);
}
bool Hidden() const
{

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

@ -6651,6 +6651,29 @@ nsContentUtils::GetFullscreenAncestor(nsIDocument* aDoc)
return nullptr;
}
/* static */
bool
nsContentUtils::IsInPointerLockContext(nsIDOMWindow* aWin)
{
if (!aWin) {
return false;
}
nsCOMPtr<nsIDocument> pointerLockedDoc =
do_QueryReferent(nsEventStateManager::sPointerLockedDoc);
if (!pointerLockedDoc || !pointerLockedDoc->GetWindow()) {
return false;
}
nsCOMPtr<nsIDOMWindow> lockTop;
pointerLockedDoc->GetWindow()->GetScriptableTop(getter_AddRefs(lockTop));
nsCOMPtr<nsIDOMWindow> top;
aWin->GetScriptableTop(getter_AddRefs(top));
return top == lockTop;
}
// static
void
nsContentUtils::ReleaseWrapper(void* aScriptObjectHolder,

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

@ -198,6 +198,8 @@
#include "nsIDOMHTMLTextAreaElement.h"
#include "nsViewportInfo.h"
#include "nsDOMEvent.h"
#include "nsIContentPermissionPrompt.h"
#include "mozilla/StaticPtr.h"
using namespace mozilla;
using namespace mozilla::dom;
@ -8042,6 +8044,8 @@ nsDocument::OnPageHide(bool aPersisted,
SetImagesNeedAnimating(false);
}
MozExitPointerLock();
// Now send out a PageHide event.
nsCOMPtr<nsIDOMEventTarget> target = aDispatchStartTarget;
if (!target) {
@ -9858,12 +9862,18 @@ public:
nsCallRequestFullScreen(Element* aElement)
: mElement(aElement),
mDoc(aElement->OwnerDoc()),
mWasCallerChrome(nsContentUtils::IsCallerChrome())
mWasCallerChrome(nsContentUtils::IsCallerChrome()),
mHadRequestPending(static_cast<nsDocument*>(mDoc.get())->
mAsyncFullscreenPending)
{
static_cast<nsDocument*>(mDoc.get())->
mAsyncFullscreenPending = true;
}
NS_IMETHOD Run()
{
static_cast<nsDocument*>(mDoc.get())->
mAsyncFullscreenPending = mHadRequestPending;
nsDocument* doc = static_cast<nsDocument*>(mDoc.get());
doc->RequestFullScreen(mElement,
mWasCallerChrome,
@ -9874,6 +9884,7 @@ public:
nsRefPtr<Element> mElement;
nsCOMPtr<nsIDocument> mDoc;
bool mWasCallerChrome;
bool mHadRequestPending;
};
void
@ -10413,6 +10424,10 @@ nsDocument::IsFullScreenEnabled(bool aCallerIsChrome, bool aLogFailure)
static void
DispatchPointerLockChange(nsIDocument* aTarget)
{
if (!aTarget) {
return;
}
nsRefPtr<nsAsyncDOMEvent> e =
new nsAsyncDOMEvent(aTarget,
NS_LITERAL_STRING("mozpointerlockchange"),
@ -10424,6 +10439,10 @@ DispatchPointerLockChange(nsIDocument* aTarget)
static void
DispatchPointerLockError(nsIDocument* aTarget)
{
if (!aTarget) {
return;
}
nsRefPtr<nsAsyncDOMEvent> e =
new nsAsyncDOMEvent(aTarget,
NS_LITERAL_STRING("mozpointerlockerror"),
@ -10432,118 +10451,182 @@ DispatchPointerLockError(nsIDocument* aTarget)
e->PostDOMEvent();
}
// Manages asynchronously requesting pointer lock. Used to dispatch an
// event to request pointer lock once fullscreen has been approved.
class nsAsyncPointerLockRequest : public nsRunnable
mozilla::StaticRefPtr<nsPointerLockPermissionRequest> gPendingPointerLockRequest;
class nsPointerLockPermissionRequest : public nsRunnable,
public nsIContentPermissionRequest
{
public:
nsPointerLockPermissionRequest(Element* aElement, bool aUserInputOrChromeCaller)
: mElement(do_GetWeakReference(aElement)),
mDocument(do_GetWeakReference(aElement->OwnerDoc())),
mUserInputOrChromeCaller(aUserInputOrChromeCaller) {}
virtual ~nsPointerLockPermissionRequest() {}
NS_DECL_ISUPPORTS
NS_DECL_NSICONTENTPERMISSIONREQUEST
NS_IMETHOD Run()
{
sInstance = nullptr;
if (mDocument && mElement) {
mDocument->RequestPointerLock(mElement);
nsCOMPtr<Element> e = do_QueryReferent(mElement);
nsCOMPtr<nsIDocument> d = do_QueryReferent(mDocument);
if (!e || !d || gPendingPointerLockRequest != this ||
e->GetCurrentDoc() != d) {
Handled();
DispatchPointerLockError(d);
return NS_OK;
}
// We're about to enter fullscreen mode.
nsDocument* doc = static_cast<nsDocument*>(d.get());
if (doc->mAsyncFullscreenPending ||
(doc->mHasFullscreenApprovedObserver && !doc->mIsApprovedForFullscreen)) {
// We're still waiting for approval.
return NS_OK;
}
if (doc->mIsApprovedForFullscreen || doc->mAllowRelocking) {
Allow();
return NS_OK;
}
// In non-fullscreen mode user input (or chrome caller) is required!
// Also, don't let the page to try to get the permission too many times.
if (!mUserInputOrChromeCaller ||
doc->mCancelledPointerLockRequests > 2) {
Handled();
DispatchPointerLockError(d);
return NS_OK;
}
// Handling a request from user input in non-fullscreen mode.
// Do a normal permission check.
nsCOMPtr<nsIContentPermissionPrompt> prompt =
do_CreateInstance(NS_CONTENT_PERMISSION_PROMPT_CONTRACTID);
if (prompt) {
prompt->Prompt(this);
}
return NS_OK;
}
static void Request(Element* aElement, nsIDocument* aDocument)
void Handled()
{
if (sInstance) {
// We already have an event instance pending. Change the requestee
// to the new pointer lock requestee.
sInstance->mElement = aElement;
sInstance->mDocument = aDocument;
} else {
// Create a new event instance. Owning ref is held by the nsIEventTarget
// to which this is dispatched.
sInstance = new nsAsyncPointerLockRequest(aElement, aDocument);
NS_DispatchToCurrentThread(sInstance);
mElement = nullptr;
mDocument = nullptr;
if (gPendingPointerLockRequest == this) {
gPendingPointerLockRequest = nullptr;
}
}
static void Cancel()
{
if (sInstance) {
// Revoke references to requesting element/document, when the
// dispatched event runs. The event will do nothing, and then be
// destroyed.
sInstance->mElement = nullptr;
sInstance->mDocument = nullptr;
}
}
private:
nsAsyncPointerLockRequest(Element* aElement, nsIDocument* aDocument)
: mElement(aElement),
mDocument(aDocument)
{
MOZ_COUNT_CTOR(nsAsyncPointerLockRequest);
}
~nsAsyncPointerLockRequest()
{
MOZ_COUNT_DTOR(nsAsyncPointerLockRequest);
}
// Reference to the instance of any pending event. This is not an owning
// reference; the nsIEventTarget to which this is dispatched holds the only
// owning reference to this instance. This reference is valid between
// an instance being created, and its Run() method being called.
static nsAsyncPointerLockRequest* sInstance;
// Element and document which reqested pointer lock.
nsCOMPtr<Element> mElement;
nsCOMPtr<nsIDocument> mDocument;
nsWeakPtr mElement;
nsWeakPtr mDocument;
bool mUserInputOrChromeCaller;
};
nsAsyncPointerLockRequest* nsAsyncPointerLockRequest::sInstance = nullptr;
nsWeakPtr nsDocument::sPendingPointerLockDoc;
nsWeakPtr nsDocument::sPendingPointerLockElement;
NS_IMPL_ISUPPORTS_INHERITED1(nsPointerLockPermissionRequest,
nsRunnable,
nsIContentPermissionRequest)
/* static */
void
nsDocument::ClearPendingPointerLockRequest(bool aDispatchErrorEvents)
NS_IMETHODIMP
nsPointerLockPermissionRequest::GetType(nsACString& aType)
{
nsAsyncPointerLockRequest::Cancel();
if (!sPendingPointerLockDoc) {
// No pending request.
return;
}
nsCOMPtr<nsIDocument> doc(do_QueryReferent(sPendingPointerLockDoc));
if (aDispatchErrorEvents) {
DispatchPointerLockError(doc);
}
nsCOMPtr<Element> element(do_QueryReferent(sPendingPointerLockElement));
#ifdef DEBUG
nsCOMPtr<Element> pointerLockedElement =
do_QueryReferent(nsEventStateManager::sPointerLockedElement);
NS_ASSERTION(pointerLockedElement != element,
"We shouldn't be clearing pointer locked flag on pointer locked element!");
#endif
if (element) {
element->ClearPointerLock();
}
sPendingPointerLockDoc = nullptr;
sPendingPointerLockElement = nullptr;
aType = "pointerLock";
return NS_OK;
}
/* static */
nsresult
nsDocument::SetPendingPointerLockRequest(Element* aElement)
NS_IMETHODIMP
nsPointerLockPermissionRequest::GetAccess(nsACString& aAccess)
{
// If there's an existing pending pointer lock request, deny it.
ClearPendingPointerLockRequest(true);
aAccess = "unused";
return NS_OK;
}
NS_ENSURE_TRUE(aElement != nullptr, NS_ERROR_FAILURE);
NS_IMETHODIMP
nsPointerLockPermissionRequest::GetPrincipal(nsIPrincipal** aPrincipal)
{
nsCOMPtr<nsIDocument> d = do_QueryReferent(mDocument);
if (d) {
NS_ADDREF(*aPrincipal = d->NodePrincipal());
}
return NS_OK;
}
sPendingPointerLockDoc = do_GetWeakReference(aElement->OwnerDoc());
sPendingPointerLockElement = do_GetWeakReference(aElement);
NS_IMETHODIMP
nsPointerLockPermissionRequest::GetWindow(nsIDOMWindow** aWindow)
{
nsCOMPtr<nsIDocument> d = do_QueryReferent(mDocument);
if (d) {
NS_IF_ADDREF(*aWindow = d->GetInnerWindow());
}
return NS_OK;
}
// Set the pointer lock flag, so that if the element is removed from
// its document we know to cancel the pending request.
aElement->SetPointerLock();
NS_IMETHODIMP
nsPointerLockPermissionRequest::GetElement(nsIDOMElement** aElement)
{
// It is enough to implement GetWindow.
*aElement = nullptr;
return NS_OK;
}
NS_IMETHODIMP
nsPointerLockPermissionRequest::Cancel()
{
nsCOMPtr<nsIDocument> d = do_QueryReferent(mDocument);
Handled();
if (d) {
static_cast<nsDocument*>(d.get())->mCancelledPointerLockRequests++;
DispatchPointerLockError(d);
}
return NS_OK;
}
NS_IMETHODIMP
nsPointerLockPermissionRequest::Allow()
{
nsCOMPtr<Element> e = do_QueryReferent(mElement);
nsCOMPtr<nsIDocument> doc = do_QueryReferent(mDocument);
nsDocument* d = static_cast<nsDocument*>(doc.get());
if (!e || !d || gPendingPointerLockRequest != this ||
e->GetCurrentDoc() != d ||
(!mUserInputOrChromeCaller && !d->mIsApprovedForFullscreen)) {
Handled();
DispatchPointerLockError(d);
return NS_OK;
}
// Mark handled here so that we don't need to call it everywhere below.
Handled();
nsCOMPtr<Element> pointerLockedElement =
do_QueryReferent(nsEventStateManager::sPointerLockedElement);
if (e == pointerLockedElement) {
DispatchPointerLockChange(d);
return NS_OK;
}
// Note, we must bypass focus change, so pass true as the last parameter!
if (!d->ShouldLockPointer(e, pointerLockedElement, true)) {
DispatchPointerLockError(d);
return NS_OK;
}
if (!d->SetPointerLock(e, NS_STYLE_CURSOR_NONE)) {
DispatchPointerLockError(d);
return NS_OK;
}
d->mCancelledPointerLockRequests = 0;
e->SetPointerLock();
nsEventStateManager::sPointerLockedElement = do_GetWeakReference(e);
nsEventStateManager::sPointerLockedDoc = do_GetWeakReference(doc);
NS_ASSERTION(nsEventStateManager::sPointerLockedElement &&
nsEventStateManager::sPointerLockedDoc,
"aElement and this should support weak references!");
DispatchPointerLockChange(d);
return NS_OK;
}
@ -10564,13 +10647,23 @@ nsDocument::Observe(nsISupports *aSubject,
return NS_OK;
}
SetApprovedForFullscreen(true);
nsCOMPtr<nsIDocument> doc(do_QueryReferent(sPendingPointerLockDoc));
if (this == doc) {
// This doc has a pointer lock request, waiting for fullscreen to be
// approved before it can be granted. Process the pointer lock request.
nsCOMPtr<Element> element(do_QueryReferent(sPendingPointerLockElement));
nsDocument::ClearPendingPointerLockRequest(false);
nsAsyncPointerLockRequest::Request(element, this);
if (gPendingPointerLockRequest) {
// We have a request pending. Create a clone of it and re-dispatch so that
// Run() method gets called again.
nsCOMPtr<Element> el =
do_QueryReferent(gPendingPointerLockRequest->mElement);
nsCOMPtr<nsIDocument> doc =
do_QueryReferent(gPendingPointerLockRequest->mDocument);
bool userInputOrChromeCaller =
gPendingPointerLockRequest->mUserInputOrChromeCaller;
gPendingPointerLockRequest->Handled();
if (doc == this && el && el->GetCurrentDoc() == doc) {
nsPointerLockPermissionRequest* clone =
new nsPointerLockPermissionRequest(el, userInputOrChromeCaller);
gPendingPointerLockRequest = clone;
nsCOMPtr<nsIRunnable> r = gPendingPointerLockRequest.get();
NS_DispatchToMainThread(r);
}
}
}
return NS_OK;
@ -10589,42 +10682,23 @@ nsDocument::RequestPointerLock(Element* aElement)
return;
}
if (!ShouldLockPointer(aElement)) {
if (!ShouldLockPointer(aElement, pointerLockedElement)) {
DispatchPointerLockError(this);
return;
}
if (!mIsApprovedForFullscreen) {
// Document isn't yet approved for fullscreen, so we must wait until
// it's been approved.
if (NS_FAILED(SetPendingPointerLockRequest(aElement))) {
NS_WARNING("Failed to make pointer lock request pending!");
DispatchPointerLockError(this);
}
return;
}
bool userInputOrChromeCaller = nsEventStateManager::IsHandlingUserInput() ||
nsContentUtils::IsCallerChrome();
// If there's an existing pending pointer lock request, deny it.
nsDocument::ClearPendingPointerLockRequest(true);
if (!SetPointerLock(aElement, NS_STYLE_CURSOR_NONE)) {
DispatchPointerLockError(this);
return;
}
aElement->SetPointerLock();
nsEventStateManager::sPointerLockedElement = do_GetWeakReference(aElement);
nsEventStateManager::sPointerLockedDoc =
do_GetWeakReference(static_cast<nsIDocument*>(this));
NS_ASSERTION(nsEventStateManager::sPointerLockedElement &&
nsEventStateManager::sPointerLockedDoc,
"aElement and this should support weak references!");
DispatchPointerLockChange(this);
gPendingPointerLockRequest =
new nsPointerLockPermissionRequest(aElement, userInputOrChromeCaller);
nsCOMPtr<nsIRunnable> r = gPendingPointerLockRequest.get();
NS_DispatchToMainThread(r);
}
bool
nsDocument::ShouldLockPointer(Element* aElement)
nsDocument::ShouldLockPointer(Element* aElement, Element* aCurrentLock,
bool aNoFocusCheck)
{
// Check if pointer lock pref is enabled
if (!Preferences::GetBool("full-screen-api.pointer-lock.enabled")) {
@ -10632,8 +10706,8 @@ nsDocument::ShouldLockPointer(Element* aElement)
return false;
}
if (aElement != GetFullScreenElement()) {
NS_WARNING("ShouldLockPointer(): Element not in fullscreen");
if (aCurrentLock && aCurrentLock->OwnerDoc() != aElement->OwnerDoc()) {
NS_WARNING("ShouldLockPointer(): Existing pointer lock element in a different document");
return false;
}
@ -10649,9 +10723,6 @@ nsDocument::ShouldLockPointer(Element* aElement)
// Check if the element is in a document with a docshell.
nsCOMPtr<nsIDocument> ownerDoc = aElement->OwnerDoc();
if (!ownerDoc) {
return false;
}
if (!nsCOMPtr<nsISupports>(ownerDoc->GetContainer())) {
return false;
}
@ -10667,6 +10738,23 @@ nsDocument::ShouldLockPointer(Element* aElement)
return false;
}
nsCOMPtr<nsIDOMWindow> top;
ownerWindow->GetScriptableTop(getter_AddRefs(top));
nsCOMPtr<nsPIDOMWindow> piTop = do_QueryInterface(top);
if (!piTop || !piTop->GetExtantDoc() ||
piTop->GetExtantDoc()->Hidden()) {
NS_WARNING("ShouldLockPointer(): Top document isn't visible.");
return false;
}
if (!aNoFocusCheck) {
mozilla::ErrorResult rv;
if (!piTop->GetExtantDoc()->HasFocus(rv)) {
NS_WARNING("ShouldLockPointer(): Top document isn't focused.");
return false;
}
}
return true;
}
@ -10728,19 +10816,15 @@ nsDocument::SetPointerLock(Element* aElement, int aCursorStyle)
}
void
nsDocument::UnlockPointer()
nsDocument::UnlockPointer(nsIDocument* aDoc)
{
// If our pointer lock request is pending awaiting authorization, deny the
// request.
ClearPendingPointerLockRequest(true);
if (!nsEventStateManager::sIsPointerLocked) {
return;
}
nsCOMPtr<nsIDocument> pointerLockedDoc =
do_QueryReferent(nsEventStateManager::sPointerLockedDoc);
if (!pointerLockedDoc) {
if (!pointerLockedDoc || (aDoc && aDoc != pointerLockedDoc)) {
return;
}
nsDocument* doc = static_cast<nsDocument*>(pointerLockedDoc.get());
@ -10750,20 +10834,21 @@ nsDocument::UnlockPointer()
nsCOMPtr<Element> pointerLockedElement =
do_QueryReferent(nsEventStateManager::sPointerLockedElement);
if (!pointerLockedElement) {
return;
if (pointerLockedElement) {
pointerLockedElement->ClearPointerLock();
}
nsEventStateManager::sPointerLockedElement = nullptr;
nsEventStateManager::sPointerLockedDoc = nullptr;
pointerLockedElement->ClearPointerLock();
static_cast<nsDocument*>(pointerLockedDoc.get())->mAllowRelocking = !!aDoc;
gPendingPointerLockRequest = nullptr;
DispatchPointerLockChange(pointerLockedDoc);
}
void
nsIDocument::UnlockPointer()
nsIDocument::UnlockPointer(nsIDocument* aDoc)
{
nsDocument::UnlockPointer();
nsDocument::UnlockPointer(aDoc);
}
NS_IMETHODIMP
@ -10805,6 +10890,12 @@ nsIDocument::GetMozPointerLockElement()
return pointerLockedElement;
}
void
nsDocument::XPCOMShutdown()
{
gPendingPointerLockRequest = nullptr;
}
#define EVENT(name_, id_, type_, struct_) \
NS_IMETHODIMP nsDocument::GetOn##name_(JSContext *cx, jsval *vp) { \
return nsINode::GetOn##name_(cx, vp); \

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

@ -94,6 +94,7 @@ class nsDOMNavigationTiming;
class nsWindowSizes;
class nsHtml5TreeOpExecutor;
class nsDocumentOnStack;
class nsPointerLockPermissionRequest;
namespace mozilla {
namespace dom {
@ -1002,9 +1003,10 @@ public:
virtual Element* GetMozFullScreenElement(mozilla::ErrorResult& rv);
void RequestPointerLock(Element* aElement);
bool ShouldLockPointer(Element* aElement);
bool ShouldLockPointer(Element* aElement, Element* aCurrentLock,
bool aNoFocusCheck = false);
bool SetPointerLock(Element* aElement, int aCursorStyle);
static void UnlockPointer();
static void UnlockPointer(nsIDocument* aDoc = nullptr);
// This method may fire a DOM event; if it does so it will happen
// synchronously.
@ -1113,6 +1115,7 @@ public:
// Set our title
virtual void SetTitle(const nsAString& aTitle, mozilla::ErrorResult& rv);
static void XPCOMShutdown();
protected:
nsresult doCreateShell(nsPresContext* aContext,
nsViewManager* aViewManager, nsStyleSet* aStyleSet,
@ -1183,15 +1186,6 @@ protected:
// is a weak reference to avoid leaks due to circular references.
nsWeakPtr mScopeObject;
// Weak reference to the document which owned the pending pointer lock
// element, at the time it requested pointer lock.
static nsWeakPtr sPendingPointerLockDoc;
// Weak reference to the element which requested pointer lock. This request
// is "pending", and will be processed once the element's document has had
// the "fullscreen" permission granted.
static nsWeakPtr sPendingPointerLockElement;
// Stack of full-screen elements. When we request full-screen we push the
// full-screen element onto this stack, and when we cancel full-screen we
// pop one off this stack, restoring the previous full-screen state
@ -1279,6 +1273,16 @@ protected:
// fullscreen will have an observer.
bool mHasFullscreenApprovedObserver:1;
friend class nsPointerLockPermissionRequest;
friend class nsCallRequestFullScreen;
// When set, trying to lock the pointer doesn't require permission from the
// user.
bool mAllowRelocking:1;
bool mAsyncFullscreenPending:1;
uint32_t mCancelledPointerLockRequests;
uint8_t mXMLDeclarationBits;
nsInterfaceHashtable<nsPtrHashKey<nsIContent>, nsPIBoxObject> *mBoxObjectTable;
@ -1311,16 +1315,6 @@ private:
nsresult CheckFrameOptions();
nsresult InitCSP(nsIChannel* aChannel);
// Sets aElement to be the pending pointer lock element. Once this document's
// node principal's URI is granted the "fullscreen" permission, the pointer
// lock request will be processed. At any one time there can be only one
// pending pointer lock request; calling this clears the previous pending
// request.
static nsresult SetPendingPointerLockRequest(Element* aElement);
// Clears any pending pointer lock request.
static void ClearPendingPointerLockRequest(bool aDispatchErrorEvents);
/**
* Find the (non-anonymous) content in this document for aFrame. It will
* be aFrame's content node if that content is in this document and not

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

@ -982,7 +982,7 @@ nsFocusManager::WindowHidden(nsIDOMWindow* aWindow)
parentWindow->SetFocusedNode(nullptr);
}
mFocusedWindow = window;
SetFocusedWindowInternal(window);
}
return NS_OK;
@ -1601,7 +1601,7 @@ nsFocusManager::Blur(nsPIDOMWindow* aWindowToClear,
if (aAncestorWindowToFocus)
aAncestorWindowToFocus->SetFocusedNode(nullptr, 0, true);
mFocusedWindow = nullptr;
SetFocusedWindowInternal(nullptr);
mFocusedContent = nullptr;
// pass 1 for the focus method when calling SendFocusOrBlurEvent just so
@ -1709,7 +1709,7 @@ nsFocusManager::Focus(nsPIDOMWindow* aWindow,
if (aWindow->TakeFocus(true, focusMethod))
aIsNewDocument = true;
mFocusedWindow = aWindow;
SetFocusedWindowInternal(aWindow);
// Update the system focus by focusing the root widget. But avoid this
// if 1) aAdjustWidgets is false or 2) aContent is a plugin that has its
@ -3362,6 +3362,57 @@ nsFocusManager::GetFocusInSelection(nsPIDOMWindow* aWindow,
while (selectionNode && selectionNode != endSelectionNode);
}
class PointerUnlocker : public nsRunnable
{
public:
PointerUnlocker()
{
MOZ_ASSERT(!PointerUnlocker::sActiveUnlocker);
PointerUnlocker::sActiveUnlocker = this;
}
~PointerUnlocker()
{
if (PointerUnlocker::sActiveUnlocker == this) {
PointerUnlocker::sActiveUnlocker = nullptr;
}
}
NS_IMETHOD Run()
{
if (PointerUnlocker::sActiveUnlocker == this) {
PointerUnlocker::sActiveUnlocker = nullptr;
}
NS_ENSURE_STATE(nsFocusManager::GetFocusManager());
nsPIDOMWindow* focused =
nsFocusManager::GetFocusManager()->GetFocusedWindow();
nsCOMPtr<nsIDocument> pointerLockedDoc =
do_QueryReferent(nsEventStateManager::sPointerLockedDoc);
if (pointerLockedDoc &&
!nsContentUtils::IsInPointerLockContext(focused)) {
nsIDocument::UnlockPointer();
}
return NS_OK;
}
static PointerUnlocker* sActiveUnlocker;
};
PointerUnlocker*
PointerUnlocker::sActiveUnlocker = nullptr;
void
nsFocusManager::SetFocusedWindowInternal(nsPIDOMWindow* aWindow)
{
if (!PointerUnlocker::sActiveUnlocker &&
nsContentUtils::IsInPointerLockContext(mFocusedWindow) &&
!nsContentUtils::IsInPointerLockContext(aWindow)) {
nsCOMPtr<nsIRunnable> runnable = new PointerUnlocker();
NS_DispatchToCurrentThread(runnable);
}
mFocusedWindow = aWindow;
}
nsresult
NS_NewFocusManager(nsIFocusManager** aResult)
{

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

@ -481,11 +481,14 @@ private:
bool aWindowShouldShowFocusRing,
bool aGettingFocus);
void SetFocusedWindowInternal(nsPIDOMWindow* aWindow);
// the currently active and front-most top-most window
nsCOMPtr<nsPIDOMWindow> mActiveWindow;
// the child or top-level window that is currently focused. This window will
// either be the same window as mActiveWindow or a descendant of it.
// Except during shutdown use SetFocusedWindowInternal to set mFocusedWindow!
nsCOMPtr<nsPIDOMWindow> mFocusedWindow;
// the currently focused content, which is always inside mFocusedWindow. This

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

@ -70,6 +70,7 @@ https://bugzilla.mozilla.org/show_bug.cgi?id=633602
const isWinXP = navigator.userAgent.indexOf("Windows NT 5.1") != -1;
const isOSXLion = navigator.userAgent.indexOf("Mac OS X 10.7") != -1;
const isOSXMtnLion = navigator.userAgent.indexOf("Mac OS X 10.8") != -1;
const isWin8 = navigator.userAgent.indexOf("Windows NT 6.2") != -1;
function finish() {
SpecialPowers.clearUserPref("full-screen-api.enabled");
@ -84,6 +85,11 @@ https://bugzilla.mozilla.org/show_bug.cgi?id=633602
finish();
return;
}
if (isWin8) {
todo(false, "Can't reliably run full-screen + pointer lock tests on Windows 8");
finish();
return;
}
if (isOSXLion || isOSXMtnLion) {
todo(false, "Can't reliably run full-screen tests on OS X Lion or Mountain Lion, see bug 744125");
finish();

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

@ -6059,6 +6059,7 @@ PresShell::HandleEvent(nsIFrame *aFrame,
nsIFrame* frame = aFrame;
if (aEvent->eventStructType == NS_TOUCH_EVENT) {
nsIDocument::UnlockPointer();
FlushPendingNotifications(Flush_Layout);
frame = GetNearestFrameContainingPresShell(this);
}
@ -6659,27 +6660,37 @@ PresShell::HandleEventInternal(nsEvent* aEvent, nsEventStatus* aStatus)
nsIDocument* doc = GetCurrentEventContent() ?
mCurrentEventContent->OwnerDoc() : nullptr;
nsIDocument* fullscreenAncestor = nullptr;
if (static_cast<const nsKeyEvent*>(aEvent)->keyCode == NS_VK_ESCAPE &&
(fullscreenAncestor = nsContentUtils::GetFullscreenAncestor(doc))) {
// Prevent default action on ESC key press when exiting
// DOM fullscreen mode. This prevents the browser ESC key
// handler from stopping all loads in the document, which
// would cause <video> loads to stop.
aEvent->mFlags.mDefaultPrevented = true;
aEvent->mFlags.mOnlyChromeDispatch = true;
if (static_cast<const nsKeyEvent*>(aEvent)->keyCode == NS_VK_ESCAPE) {
if ((fullscreenAncestor = nsContentUtils::GetFullscreenAncestor(doc))) {
// Prevent default action on ESC key press when exiting
// DOM fullscreen mode. This prevents the browser ESC key
// handler from stopping all loads in the document, which
// would cause <video> loads to stop.
aEvent->mFlags.mDefaultPrevented = true;
aEvent->mFlags.mOnlyChromeDispatch = true;
if (aEvent->message == NS_KEY_UP) {
// ESC key released while in DOM fullscreen mode.
// If fullscreen is running in content-only mode, exit the target
// doctree branch from fullscreen, otherwise fully exit all
// browser windows and documents from fullscreen mode.
// Note: in the content-only fullscreen case, we pass the
// fullscreenAncestor since |doc| may not actually be fullscreen
// here, and ExitFullscreen() has no affect when passed a
// non-fullscreen document.
nsIDocument::ExitFullscreen(
nsContentUtils::IsFullscreenApiContentOnly() ? fullscreenAncestor : nullptr,
/* async */ true);
if (aEvent->message == NS_KEY_UP) {
// ESC key released while in DOM fullscreen mode.
// If fullscreen is running in content-only mode, exit the target
// doctree branch from fullscreen, otherwise fully exit all
// browser windows and documents from fullscreen mode.
// Note: in the content-only fullscreen case, we pass the
// fullscreenAncestor since |doc| may not actually be fullscreen
// here, and ExitFullscreen() has no affect when passed a
// non-fullscreen document.
nsIDocument::ExitFullscreen(
nsContentUtils::IsFullscreenApiContentOnly() ? fullscreenAncestor : nullptr,
/* async */ true);
}
}
nsCOMPtr<nsIDocument> pointerLockedDoc =
do_QueryReferent(nsEventStateManager::sPointerLockedDoc);
if (pointerLockedDoc) {
aEvent->mFlags.mDefaultPrevented = true;
aEvent->mFlags.mOnlyChromeDispatch = true;
if (aEvent->message == NS_KEY_UP) {
nsIDocument::UnlockPointer();
}
}
}
// Else not full-screen mode or key code is unrestricted, fall

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

@ -113,6 +113,7 @@ using namespace mozilla::system;
#include "nsApplicationCacheService.h"
#include "mozilla/dom/time/DateCacheCleaner.h"
#include "nsIMEStateManager.h"
#include "nsDocument.h"
extern void NS_ShutdownEventTargetChainItemRecyclePool();
@ -387,4 +388,6 @@ nsLayoutStatics::Shutdown()
ContentParent::ShutDown();
nsRefreshDriver::Shutdown();
nsDocument::XPCOMShutdown();
}