Bug 684620 - Implement cross process fullscreen API for B2G apps and web content. r=jlebar

* * *
Bug 684620 - Fix in-process fullscreen in B2G. r=?
This commit is contained in:
Chris Pearce 2012-07-31 14:09:31 +12:00
parent e3bcda27f0
commit cd61327986
13 changed files with 380 additions and 29 deletions

View File

@ -383,6 +383,12 @@ Services.obs.addObserver(function onSystemMessage(subject, topic, data) {
});
}, 'system-messages-open-app', false);
Services.obs.addObserver(function(aSubject, aTopic, aData) {
shell.sendEvent(shell.contentBrowser.contentWindow,
"mozChromeEvent", { type: "fullscreenoriginchange",
fullscreenorigin: aData } );
}, "fullscreen-origin-change", false);
(function Repl() {
if (!Services.prefs.getBoolPref('b2g.remote-js.enabled')) {
return;

View File

@ -92,8 +92,8 @@ class Element;
} // namespace mozilla
#define NS_IDOCUMENT_IID \
{ 0x8c6a1e62, 0xd5ad, 0x4297, \
{ 0xb9, 0x41, 0x64, 0x49, 0x22, 0x2e, 0xc4, 0xf0 } }
{ 0xbd70ee06, 0x2a7d, 0x4258, \
{ 0x86, 0x4b, 0xbd, 0x28, 0xad, 0x9f, 0xd1, 0x41 } }
// Flag for AddStyleSheet().
#define NS_STYLESHEET_FROM_CATALOG (1 << 0)
@ -723,6 +723,27 @@ public:
*/
virtual void AsyncRequestFullScreen(Element* aElement) = 0;
/**
* Called when a frame in a child process has entered fullscreen or when a
* fullscreen frame in a child process changes to another origin.
* aFrameElement is the frame element which contains the child-process
* fullscreen document, and aNewOrigin is the origin of the new fullscreen
* document.
*/
virtual nsresult RemoteFrameFullscreenChanged(nsIDOMElement* aFrameElement,
const nsAString& aNewOrigin) = 0;
/**
* Called when a frame in a remote child document has rolled back fullscreen
* so that all its fullscreen element stacks are empty; we must continue the
* rollback in this parent process' doc tree branch which is fullscreen.
* Note that only one branch of the document tree can have its documents in
* fullscreen state at one time. We're in inconsistent state if the a
* fullscreen document has a parent and that parent isn't fullscreen. We
* preserve this property across process boundaries.
*/
virtual nsresult RemoteFrameFullscreenReverted() = 0;
/**
* Restores the previous full-screen element to full-screen status. If there
* is no former full-screen element, this exits full-screen, moving the

View File

@ -8499,7 +8499,7 @@ nsDocument::MozCancelFullScreen()
}
// Runnable to set window full-screen mode. Used as a script runner
// to ensure we only call nsGlobalWindow::SetFullScreen() when it's safe to
// to ensure we only call nsGlobalWindow::SetFullScreen() when it's safe to
// run script. nsGlobalWindow::SetFullScreen() dispatches a synchronous event
// (handled in chome code) which is unsafe to run if this is called in
// nsGenericElement::UnbindFromTree().
@ -8547,13 +8547,54 @@ nsIDocument::ExitFullScreen(bool aRunAsync)
nsDocument::ExitFullScreen();
}
// Returns true if the document is a direct child of a cross process parent
// mozbrowser iframe. This is the case when the document has a null parent,
// and its DocShell reports that it is a browser frame.
static bool
ResetFullScreen(nsIDocument* aDocument, void* aData) {
HasCrossProcessParent(nsIDocument* aDocument)
{
if (XRE_GetProcessType() != GeckoProcessType_Content) {
return false;
}
if (aDocument->GetParentDocument() != nullptr) {
return false;
}
nsPIDOMWindow* win = aDocument->GetWindow();
if (!win) {
return false;
}
nsCOMPtr<nsIDocShell> docShell = win->GetDocShell();
if (!docShell) {
return false;
}
bool isBrowserElement = false;
docShell->GetIsBrowserElement(&isBrowserElement);
return isBrowserElement;
}
static bool
ResetFullScreen(nsIDocument* aDocument, void* aData)
{
if (aDocument->IsFullScreenDoc()) {
static_cast<nsDocument*>(aDocument)->CleanupFullscreenState();
NS_ASSERTION(!aDocument->IsFullScreenDoc(), "Should reset full-screen");
nsTArray<nsIDocument*>* changed = reinterpret_cast<nsTArray<nsIDocument*>*>(aData);
changed->AppendElement(aDocument);
if (HasCrossProcessParent(aDocument)) {
// We're at the top of the content-process side doc tree. Ask the parent
// process to exit fullscreen.
nsCOMPtr<nsIObserverService> os = mozilla::services::GetObserverService();
os->NotifyObservers(aDocument, "ask-parent-to-exit-fullscreen", nullptr);
}
// Dispatch a notification so that if this document has any
// cross-process subdocuments, they'll be notified to exit fullscreen.
// The BrowserElementParent listens for this event and performs the
// cross process notification if it has a remote child process.
nsCOMPtr<nsIObserverService> os = mozilla::services::GetObserverService();
os->NotifyObservers(aDocument, "ask-children-to-exit-fullscreen", nullptr);
aDocument->EnumerateSubDocuments(ResetFullScreen, aData);
}
return true;
@ -8623,8 +8664,15 @@ nsDocument::RestorePreviousFullScreenState()
UnlockPointer();
}
// Clear full-screen stacks in all descendant documents, bottom up.
nsCOMPtr<nsIDocument> fullScreenDoc(do_QueryReferent(sFullScreenDoc));
// The fullscreen document may contain a <iframe mozbrowser> element which
// has a cross process child. So send a notification so that its browser
// parent will send a message to its child process to also exit fullscreen.
nsCOMPtr<nsIObserverService> os = mozilla::services::GetObserverService();
os->NotifyObservers(fullScreenDoc, "ask-children-to-exit-fullscreen", nullptr);
// Clear full-screen stacks in all descendant in process documents, bottom up.
nsIDocument* doc = fullScreenDoc;
while (doc != this) {
NS_ASSERTION(doc->IsFullScreenDoc(), "Should be full-screen doc");
@ -8641,6 +8689,12 @@ nsDocument::RestorePreviousFullScreenState()
UnlockPointer();
DispatchFullScreenChange(doc);
if (static_cast<nsDocument*>(doc)->mFullScreenStack.IsEmpty()) {
if (HasCrossProcessParent(doc)) {
// Send notification to the parent process to tell it to rollback to
// the previous fullscreen elements in its fullscreen element stacks.
nsCOMPtr<nsIObserverService> os = mozilla::services::GetObserverService();
os->NotifyObservers(doc, "ask-parent-to-rollback-fullscreen", nullptr);
}
// Full-screen stack in document is empty. Go back up to the parent
// document. We'll pop the containing element off its stack, and use
// its next full-screen element as the full-screen element.
@ -8665,6 +8719,18 @@ nsDocument::RestorePreviousFullScreenState()
e->PostDOMEvent();
}
}
if (!nsContentUtils::HaveEqualPrincipals(doc, fullScreenDoc)) {
// The origin which is fullscreen changed. Send a notification to
// the root process so that a warning or approval UI can be shown
// as necessary.
nsAutoString origin;
nsContentUtils::GetUTFOrigin(doc->NodePrincipal(), origin);
nsIDocument* root = nsContentUtils::GetRootDocument(doc);
nsCOMPtr<nsIObserverService> os = mozilla::services::GetObserverService();
os->NotifyObservers(root, "fullscreen-origin-change", origin.get());
}
sFullScreenDoc = do_GetWeakReference(doc);
break;
}
@ -8700,7 +8766,9 @@ public:
NS_IMETHOD Run()
{
nsDocument* doc = static_cast<nsDocument*>(mDoc.get());
doc->RequestFullScreen(mElement, mWasCallerChrome);
doc->RequestFullScreen(mElement,
mWasCallerChrome,
/* aNotifyOnOriginChange */ true);
return NS_OK;
}
@ -8867,7 +8935,7 @@ IsInActiveTab(nsIDocument* aDoc)
if (!isActive) {
return false;
}
nsCOMPtr<nsIDocShellTreeItem> dsti = do_QueryInterface(container);
if (!dsti) {
return false;
@ -8896,8 +8964,43 @@ IsInActiveTab(nsIDocument* aDoc)
return activeWindow == rootWin;
}
nsresult nsDocument::RemoteFrameFullscreenChanged(nsIDOMElement* aFrameElement,
const nsAString& aOrigin)
{
// Ensure the frame element is the fullscreen element in this document.
// If the frame element is already the fullscreen element in this document,
// this has no effect.
nsCOMPtr<nsIContent> content(do_QueryInterface(aFrameElement));
RequestFullScreen(content->AsElement(),
/* aWasCallerChrome */ false,
/* aNotifyOnOriginChange */ false);
// Origin changed in child process, send notifiction, so that chrome can
// update the UI to reflect the fullscreen origin change if necessary.
// The BrowserElementChild listens on this, and forwards it over its
// parent process, where it is redispatched. Chrome (in the root process,
// which could be *this* process) listens for this notification so that
// it can show a warning or approval UI.
if (!aOrigin.IsEmpty()) {
nsCOMPtr<nsIObserverService> os = mozilla::services::GetObserverService();
os->NotifyObservers(nsContentUtils::GetRootDocument(this),
"fullscreen-origin-change",
PromiseFlatString(aOrigin).get());
}
return NS_OK;
}
nsresult nsDocument::RemoteFrameFullscreenReverted()
{
RestorePreviousFullScreenState();
return NS_OK;
}
void
nsDocument::RequestFullScreen(Element* aElement, bool aWasCallerChrome)
nsDocument::RequestFullScreen(Element* aElement,
bool aWasCallerChrome,
bool aNotifyOnOriginChange)
{
NS_ASSERTION(aElement,
"Must pass non-null element to nsDocument::RequestFullScreen");
@ -8922,7 +9025,7 @@ nsDocument::RequestFullScreen(Element* aElement, bool aWasCallerChrome)
}
if (GetFullScreenElement() &&
!nsContentUtils::ContentIsDescendantOf(aElement, GetFullScreenElement())) {
// If this document is full-screen, only grant full-screen requests from
// If this document is full-screen, only grant full-screen requests from
// a descendent of the current full-screen element.
LogFullScreenDenied(true, "FullScreenDeniedNotDescendant", this);
return;
@ -8958,12 +9061,12 @@ nsDocument::RequestFullScreen(Element* aElement, bool aWasCallerChrome)
// Remember the root document, so that if a full-screen document is hidden
// we can reset full-screen state in the remaining visible full-screen documents.
nsIDocument* fullScreenDoc = nsContentUtils::GetRootDocument(this);
sFullScreenRootDoc = do_GetWeakReference(fullScreenDoc);
nsIDocument* fullScreenRootDoc = nsContentUtils::GetRootDocument(this);
sFullScreenRootDoc = do_GetWeakReference(fullScreenRootDoc);
// If a document is already in fullscreen, then unlock the mouse pointer
// before setting a new document to fullscreen
if (fullScreenDoc) {
if (sFullScreenDoc) {
UnlockPointer();
}
@ -8981,15 +9084,18 @@ nsDocument::RequestFullScreen(Element* aElement, bool aWasCallerChrome)
DebugOnly<bool> x = FullScreenStackPush(aElement);
NS_ASSERTION(x, "Full-screen state of requesting doc should always change!");
changed.AppendElement(this);
// Propagate up the document hierarchy, setting the full-screen element as
// the element's container in ancestor documents. This also sets the
// appropriate css styles as well. Note we don't propagate down the
// document hierarchy, the full-screen element (or its container) is not
// visible there.
// visible there. Stop when we reach the root document.
nsIDocument* child = this;
nsIDocument* parent;
while ((parent = child->GetParentDocument())) {
while (true) {
nsIDocument* parent = child->GetParentDocument();
if (!parent) {
break;
}
Element* element = parent->FindContentForSubDocument(child)->AsElement();
if (static_cast<nsDocument*>(parent)->FullScreenStackPush(element)) {
changed.AppendElement(parent);
@ -9012,10 +9118,12 @@ nsDocument::RequestFullScreen(Element* aElement, bool aWasCallerChrome)
// If this document hasn't already been approved in this session,
// check to see if the user has granted the fullscreen access
// to the document's principal's host, if it has one.
// to the document's principal's host, if it has one. Note that documents
// in web apps which are the same origin as the web app are considered
// trusted and so are automatically approved.
if (!mIsApprovedForFullscreen) {
mIsApprovedForFullscreen =
GetWindow()->IsPartOfApp() ||
GetWindow()->IsInAppOrigin() ||
nsContentUtils::IsSitePermAllow(NodePrincipal(), "fullscreen");
}
@ -9052,6 +9160,21 @@ nsDocument::RequestFullScreen(Element* aElement, bool aWasCallerChrome)
"GetMozFullScreenElement should match GetFullScreenElement()");
#endif
// The origin which is fullscreen changed, send a notifiction so that the
// root document knows the origin of the document which requested fullscreen.
// This is used for the fullscreen approval UI. If we're in a child
// process, the root BrowserElementChild listens for this notification,
// and forwards it across to its BrowserElementParent, which
// re-broadcasts the message for the root document in its process.
if (aNotifyOnOriginChange &&
!nsContentUtils::HaveEqualPrincipals(previousFullscreenDoc, this)) {
nsCOMPtr<nsIObserverService> os = mozilla::services::GetObserverService();
nsIDocument* root = nsContentUtils::GetRootDocument(this);
nsAutoString origin;
nsContentUtils::GetUTFOrigin(NodePrincipal(), origin);
os->NotifyObservers(root, "fullscreen-origin-change", origin.get());
}
// Make the window full-screen. Note we must make the state changes above
// before making the window full-screen, as then the document reports as
// being in full-screen mode when the chrome "fullscreen" event fires,

View File

@ -937,14 +937,26 @@ public:
virtual void RestorePreviousFullScreenState();
virtual bool IsFullScreenDoc();
virtual void SetApprovedForFullscreen(bool aIsApproved);
virtual nsresult RemoteFrameFullscreenChanged(nsIDOMElement* aFrameElement,
const nsAString& aNewOrigin);
virtual nsresult RemoteFrameFullscreenReverted();
static void ExitFullScreen();
// This is called asynchronously by nsIDocument::AsyncRequestFullScreen()
// to move document into full-screen mode if allowed. aWasCallerChrome
// to move this document into full-screen mode if allowed. aWasCallerChrome
// should be true when nsIDocument::AsyncRequestFullScreen() was called
// by chrome code.
void RequestFullScreen(Element* aElement, bool aWasCallerChrome);
// by chrome code. aNotifyOnOriginChange denotes whether we should send a
// fullscreen-origin-change notification if requesting fullscreen in this
// document causes the origin which is fullscreen to change. We may want to
// *not* send this notification if we're calling RequestFullscreen() as part
// of a continuation of a request in a subdocument, whereupon the caller will
// need to send the notification with the origin of the document which
// originally requested fullscreen, not *this* document's origin.
void RequestFullScreen(Element* aElement,
bool aWasCallerChrome,
bool aNotifyOnOriginChange);
// Removes all elements from the full-screen stack, removing full-scren
// styles from the top element in the stack.

View File

@ -2342,3 +2342,4 @@ nsFrameLoader::SetRemoteBrowser(nsITabParent* aTabParent)
"remote-browser-frame-shown", NULL);
}
}

View File

@ -4762,14 +4762,18 @@ static const char*
GetFullScreenError(nsIDocument* aDoc)
{
nsCOMPtr<nsPIDOMWindow> win = aDoc->GetWindow();
if (win && win->IsPartOfApp()) {
if (win && win->IsInAppOrigin()) {
// Request is in a web app and in the same origin as the web app.
// Don't enforce as strict security checks for web apps, the user
// is supposed to have trust in them. However documents cross-origin
// to the web app must still confirm to the normal security checks.
return nullptr;
}
if (!nsContentUtils::IsRequestFullScreenAllowed()) {
return "FullScreenDeniedNotInputDriven";
}
if (nsContentUtils::IsSitePermDeny(aDoc->NodePrincipal(), "fullscreen")) {
return "FullScreenDeniedBlocked";
}
@ -4784,6 +4788,8 @@ nsresult nsGenericElement::MozRequestFullScreen()
// This stops the full-screen from being abused similar to the popups of old,
// and it also makes it harder for bad guys' script to go full-screen and
// spoof the browser chrome/window and phish logins etc.
// Note that requests for fullscreen inside a web app's origin are exempt
// from this restriction.
const char* error = GetFullScreenError(OwnerDoc());
if (error) {
nsContentUtils::ReportToConsole(nsIScriptError::warningFlag,
@ -4803,3 +4809,4 @@ nsresult nsGenericElement::MozRequestFullScreen()
return NS_OK;
}

View File

@ -2593,3 +2593,37 @@ nsDOMWindowUtils::GetApp(mozIDOMApplication** aApplication)
return static_cast<nsGlobalWindow*>(window.get())->GetApp(aApplication);
}
nsresult
nsDOMWindowUtils::RemoteFrameFullscreenChanged(nsIDOMElement* aFrameElement,
const nsAString& aNewOrigin)
{
nsCOMPtr<nsPIDOMWindow> window = do_QueryReferent(mWindow);
NS_ENSURE_STATE(window);
nsCOMPtr<nsIDocument> doc(do_QueryInterface(window->GetExtantDocument()));
NS_ENSURE_STATE(doc);
doc->RemoteFrameFullscreenChanged(aFrameElement, aNewOrigin);
return NS_OK;
}
nsresult
nsDOMWindowUtils::RemoteFrameFullscreenReverted()
{
nsCOMPtr<nsPIDOMWindow> window = do_QueryReferent(mWindow);
NS_ENSURE_STATE(window);
nsCOMPtr<nsIDocument> doc(do_QueryInterface(window->GetExtantDocument()));
NS_ENSURE_STATE(doc);
doc->RemoteFrameFullscreenReverted();
return NS_OK;
}
nsresult
nsDOMWindowUtils::ExitFullscreen()
{
nsIDocument::ExitFullScreen(/* async = */ false);
return NS_OK;
}

View File

@ -10661,6 +10661,34 @@ nsGlobalWindow::SetIsApp(bool aValue)
mIsApp = aValue ? TriState_True : TriState_False;
}
bool
nsGlobalWindow::IsInAppOrigin()
{
FORWARD_TO_OUTER(IsInAppOrigin, (), false);
nsIPrincipal* principal = GetPrincipal();
NS_ENSURE_TRUE(principal != nullptr, false);
// We go trough all window parents until we find one with |mIsApp| set to
// something. If none is found, we are not part of an application.
for (nsGlobalWindow* w = static_cast<nsGlobalWindow*>(this); w;
w = static_cast<nsGlobalWindow*>(w->GetParentInternal())) {
if (w->mIsApp == TriState_True) {
// The window should be part of an application.
MOZ_ASSERT(w->mApp);
bool sameOrigin = false;
return w->mAppPrincipal &&
principal &&
NS_SUCCEEDED(principal->Equals(w->mAppPrincipal, &sameOrigin)) &&
sameOrigin;
} else if (w->mIsApp == TriState_False) {
return false;
}
}
return false;
}
bool
nsGlobalWindow::IsPartOfApp()
{
@ -10691,6 +10719,14 @@ nsGlobalWindow::SetApp(const nsAString& aManifestURL)
return NS_ERROR_FAILURE;
}
nsCOMPtr<nsIURI> uri;
nsresult res = NS_NewURI(getter_AddRefs(uri), aManifestURL);
NS_ENSURE_SUCCESS(res, res);
nsIScriptSecurityManager* ssm = nsContentUtils::GetSecurityManager();
res = ssm->GetSimpleCodebasePrincipal(uri, getter_AddRefs(mAppPrincipal));
NS_ENSURE_SUCCESS(res, res);
mApp = app.forget();
return NS_OK;

View File

@ -385,6 +385,7 @@ public:
virtual NS_HIDDEN_(void) RefreshCompartmentPrincipal();
virtual NS_HIDDEN_(nsresult) SetFullScreenInternal(bool aIsFullScreen, bool aRequireTrust);
virtual NS_HIDDEN_(bool) IsPartOfApp();
virtual NS_HIDDEN_(bool) IsInAppOrigin();
// nsIDOMStorageIndexedDB
NS_DECL_NSIDOMSTORAGEINDEXEDDB
@ -978,6 +979,9 @@ protected:
// iframe which is neither mozBrowser nor mozApp.
TriState mIsApp : 2;
// Principal of the web app running in this window, if any.
nsCOMPtr<nsIPrincipal> mAppPrincipal;
nsCOMPtr<nsIScriptContext> mContext;
nsWeakPtr mOpener;
nsCOMPtr<nsIControllers> mControllers;

View File

@ -602,6 +602,12 @@ public:
*/
virtual bool IsPartOfApp() = 0;
/**
* Returns true of this window is part of an we app, and this window has
* the same origin (principal) as the web app.
*/
virtual bool IsInAppOrigin() = 0;
protected:
// The nsPIDOMWindow constructor. The aOuterWindow argument should
// be null if and only if the created window itself is an outer

View File

@ -62,6 +62,10 @@ function BrowserElementChild() {
};
BrowserElementChild.prototype = {
QueryInterface: XPCOMUtils.generateQI([Ci.nsIObserver,
Ci.nsISupportsWeakReference]),
_init: function() {
debug("Starting up.");
sendAsyncMsg("hello");
@ -137,6 +141,7 @@ BrowserElementChild.prototype = {
addMsgListener("unblock-modal-prompt", this._recvStopWaiting);
addMsgListener("fire-ctx-callback", this._recvFireCtxCallback);
addMsgListener("owner-visibility-change", this._recvOwnerVisibilityChange);
addMsgListener("exit-fullscreen", this._recvExitFullscreen.bind(this));
let els = Cc["@mozilla.org/eventlistenerservice;1"]
.getService(Ci.nsIEventListenerService);
@ -161,6 +166,35 @@ BrowserElementChild.prototype = {
els.addSystemEventListener(global, 'scroll',
this._scrollEventHandler.bind(this),
/* useCapture = */ false);
Services.obs.addObserver(this,
"fullscreen-origin-change",
/* ownsWeak = */ true);
Services.obs.addObserver(this,
'ask-parent-to-exit-fullscreen',
/* ownsWeak = */ true);
Services.obs.addObserver(this,
'ask-parent-to-rollback-fullscreen',
/* ownsWeak = */ true);
},
observe: function(subject, topic, data) {
// Ignore notifications not about our document.
if (subject != content.document)
return;
switch (topic) {
case 'fullscreen-origin-change':
sendAsyncMsg('fullscreen-origin-change', data);
break;
case 'ask-parent-to-exit-fullscreen':
sendAsyncMsg('exit-fullscreen');
break;
case 'ask-parent-to-rollback-fullscreen':
sendAsyncMsg('rollback-fullscreen');
break;
}
},
_tryGetInnerWindowID: function(win) {
@ -293,6 +327,13 @@ BrowserElementChild.prototype = {
win.modalDepth--;
},
_recvExitFullscreen: function() {
var utils = content.document.defaultView
.QueryInterface(Ci.nsIInterfaceRequestor)
.getInterface(Ci.nsIDOMWindowUtils);
utils.exitFullscreen();
},
_titleChangedHandler: function(e) {
debug("Got titlechanged: (" + e.target.title + ")");
var win = e.target.defaultView;

View File

@ -83,17 +83,17 @@ BrowserElementParentFactory.prototype = {
_observeInProcessBrowserFrameShown: function(frameLoader) {
debug("In-process browser frame shown " + frameLoader);
this._createBrowserElementParent(frameLoader);
this._createBrowserElementParent(frameLoader, /* hasRemoteFrame = */ false);
},
_observeRemoteBrowserFrameShown: function(frameLoader) {
debug("Remote browser frame shown " + frameLoader);
this._createBrowserElementParent(frameLoader);
this._createBrowserElementParent(frameLoader, /* hasRemoteFrame = */ true);
},
_createBrowserElementParent: function(frameLoader) {
_createBrowserElementParent: function(frameLoader, hasRemoteFrame) {
let frameElement = frameLoader.QueryInterface(Ci.nsIFrameLoader).ownerElement;
this._bepMap.set(frameElement, new BrowserElementParent(frameLoader));
this._bepMap.set(frameElement, new BrowserElementParent(frameLoader, hasRemoteFrame));
},
observe: function(subject, topic, data) {
@ -119,10 +119,11 @@ BrowserElementParentFactory.prototype = {
},
};
function BrowserElementParent(frameLoader) {
function BrowserElementParent(frameLoader, hasRemoteFrame) {
debug("Creating new BrowserElementParent object for " + frameLoader);
this._domRequestCounter = 0;
this._pendingDOMRequests = {};
this._hasRemoteFrame = hasRemoteFrame;
this._frameElement = frameLoader.QueryInterface(Ci.nsIFrameLoader).ownerElement;
if (!this._frameElement) {
@ -162,6 +163,12 @@ function BrowserElementParent(frameLoader) {
addMessageListener('got-screenshot', this._gotDOMRequestResult);
addMessageListener('got-can-go-back', this._gotDOMRequestResult);
addMessageListener('got-can-go-forward', this._gotDOMRequestResult);
addMessageListener('fullscreen-origin-change', this._remoteFullscreenOriginChange);
addMessageListener('rollback-fullscreen', this._remoteFrameFullscreenReverted);
addMessageListener('exit-fullscreen', this._exitFullscreen);
let os = Cc["@mozilla.org/observer-service;1"].getService(Ci.nsIObserverService);
os.addObserver(this, 'ask-children-to-exit-fullscreen', /* ownsWeak = */ true);
function defineMethod(name, fn) {
XPCNativeWrapper.unwrap(self._frameElement)[name] = function() {
@ -202,6 +209,10 @@ function BrowserElementParent(frameLoader) {
}
BrowserElementParent.prototype = {
QueryInterface: XPCOMUtils.generateQI([Ci.nsIObserver,
Ci.nsISupportsWeakReference]),
/**
* You shouldn't touch this._frameElement or this._window if _isAlive is
* false. (You'll likely get an exception if you do.)
@ -216,6 +227,11 @@ BrowserElementParent.prototype = {
return this._frameElement.ownerDocument.defaultView;
},
get _windowUtils() {
return this._window.QueryInterface(Ci.nsIInterfaceRequestor)
.getInterface(Ci.nsIDOMWindowUtils);
},
_sendAsyncMsg: function(msg, data) {
this._frameElement.QueryInterface(Ci.nsIFrameLoaderOwner)
.frameLoader
@ -435,6 +451,27 @@ BrowserElementParent.prototype = {
this._sendAsyncMsg('owner-visibility-change',
{visible: !this._window.document.mozHidden});
},
_exitFullscreen: function() {
this._windowUtils.exitFullscreen();
},
_remoteFullscreenOriginChange: function(data) {
let origin = data.json;
this._windowUtils.remoteFrameFullscreenChanged(this._frameElement, origin);
},
_remoteFrameFullscreenReverted: function(data) {
this._windowUtils.remoteFrameFullscreenReverted();
},
observe: function(subject, topic, data) {
if (topic == 'ask-children-to-exit-fullscreen' &&
this._isAlive() &&
this._frameElement.ownerDocument == subject &&
this._hasRemoteFrame)
this._sendAsyncMsg('exit-fullscreen');
},
};
var NSGetFactory = XPCOMUtils.generateNSGetFactory([BrowserElementParentFactory]);

View File

@ -40,7 +40,7 @@ interface nsIDOMTouch;
interface nsIDOMClientRect;
interface mozIDOMApplication;
[scriptable, uuid(858578f1-9653-4d5c-821a-07479bf2d9b2)]
[scriptable, uuid(b276a71e-21ee-4be6-894d-4039523e1ad8)]
interface nsIDOMWindowUtils : nsISupports {
/**
@ -761,6 +761,29 @@ interface nsIDOMWindowUtils : nsISupports {
in long aX,
in long aY);
/**
* Called when the remote child frame has changed its fullscreen state,
* when entering fullscreen, and when the origin which is fullscreen changes.
* aFrameElement is the iframe element which contains the child-process
* fullscreen document, and aNewOrigin is the origin of the new fullscreen
* document.
*/
void remoteFrameFullscreenChanged(in nsIDOMElement aFrameElement,
in AString aNewOrigin);
/**
* Called when the remote frame has popped all fullscreen elements off its
* stack, so that the operation can complete on the parent side.
*/
void remoteFrameFullscreenReverted();
/**
* Called when the child frame has fully exit fullscreen, so that the parent
* process can also fully exit.
*/
void exitFullscreen();
// NOTE: following values are same as NS_QUERY_* in nsGUIEvent.h
/**