Bug 1170894 - Implement nsIFrameLoader::SwitchProcessAndLoadURI. r=smaug

This commit is contained in:
Kan-Ru Chen 2015-08-31 18:21:40 +08:00
parent bb3240f4f5
commit 57f614afaf
9 changed files with 255 additions and 21 deletions

View File

@ -275,6 +275,42 @@ nsFrameLoader::LoadURI(nsIURI* aURI)
return rv;
}
NS_IMETHODIMP
nsFrameLoader::SwitchProcessAndLoadURI(nsIURI* aURI)
{
nsCOMPtr<nsIURI> URIToLoad = aURI;
nsRefPtr<TabParent> tp = nullptr;
MutableTabContext context;
nsCOMPtr<mozIApplication> ownApp = GetOwnApp();
nsCOMPtr<mozIApplication> containingApp = GetContainingApp();
bool tabContextUpdated = true;
if (ownApp) {
tabContextUpdated = context.SetTabContextForAppFrame(ownApp, containingApp);
} else if (OwnerIsBrowserFrame()) {
// The |else| above is unnecessary; OwnerIsBrowserFrame() implies !ownApp.
tabContextUpdated = context.SetTabContextForBrowserFrame(containingApp);
} else {
tabContextUpdated = context.SetTabContextForNormalFrame();
}
NS_ENSURE_STATE(tabContextUpdated);
nsCOMPtr<Element> ownerElement = mOwnerContent;
tp = ContentParent::CreateBrowserOrApp(context, ownerElement, nullptr);
if (!tp) {
return NS_ERROR_FAILURE;
}
mRemoteBrowserShown = false;
nsresult rv = SwapRemoteBrowser(tp);
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
LoadURI(URIToLoad);
return NS_OK;
}
NS_IMETHODIMP
nsFrameLoader::SetIsPrerendered()
{
@ -2603,6 +2639,57 @@ nsFrameLoader::SetRemoteBrowser(nsITabParent* aTabParent)
ShowRemoteFrame(ScreenIntSize(0, 0));
}
nsresult
nsFrameLoader::SwapRemoteBrowser(nsITabParent* aTabParent)
{
nsRefPtr<TabParent> newParent = TabParent::GetFrom(aTabParent);
if (!newParent || !mRemoteBrowser) {
return NS_ERROR_DOM_INVALID_STATE_ERR;
}
if (!IsRemoteFrame()) {
NS_WARNING("Switching from in-process to out-of-process is not supported.");
return NS_ERROR_NOT_IMPLEMENTED;
}
if (!OwnerIsBrowserOrAppFrame()) {
NS_WARNING("Switching process for non-mozbrowser/app frame is not supported.");
return NS_ERROR_NOT_IMPLEMENTED;
}
if (newParent == mRemoteBrowser) {
return NS_OK;
}
nsCOMPtr<nsIObserverService> os = services::GetObserverService();
if (os) {
os->NotifyObservers(NS_ISUPPORTS_CAST(nsIFrameLoader*, this),
"frameloader-message-manager-will-change", nullptr);
}
mRemoteBrowser->CacheFrameLoader(nullptr);
mRemoteBrowser->SetOwnerElement(nullptr);
mRemoteBrowser->Detach();
mRemoteBrowser->Destroy();
if (mMessageManager) {
mMessageManager->Disconnect();
mMessageManager = nullptr;
}
mRemoteBrowser = newParent;
mRemoteBrowser->Attach(this);
mChildID = mRemoteBrowser->Manager()->ChildID();
ReallyLoadFrameScripts();
InitializeBrowserAPI();
if (os) {
os->NotifyObservers(NS_ISUPPORTS_CAST(nsIFrameLoader*, this),
"frameloader-message-manager-changed", nullptr);
}
if (!mRemoteBrowserShown) {
ShowRemoteFrame(ScreenIntSize(0, 0));
}
return NS_OK;
}
void
nsFrameLoader::SetDetachedSubdocView(nsView* aDetachedViews,
nsIDocument* aContainerDoc)

View File

@ -181,6 +181,8 @@ public:
*/
void SetRemoteBrowser(nsITabParent* aTabParent);
nsresult SwapRemoteBrowser(nsITabParent* aTabParent);
/**
* Stashes a detached view on the frame loader. We do this when we're
* destroying the nsSubDocumentFrame. If the nsSubdocumentFrame is

View File

@ -16,7 +16,7 @@ interface nsIDOMElement;
interface nsITabParent;
interface nsILoadContext;
[scriptable, builtinclass, uuid(d24f9330-ae4e-11e4-ab27-0800200c9a66)]
[scriptable, builtinclass, uuid(c6e00815-b7a1-4544-b309-a85b86cb1747)]
interface nsIFrameLoader : nsISupports
{
/**
@ -49,6 +49,14 @@ interface nsIFrameLoader : nsISupports
*/
void loadURI(in nsIURI aURI);
/**
* Loads the specified URI in this frame but using a different process.
* Behaves identically to loadURI, except that this method only works
* with remote frame.
* Throws an exception with non-remote frames.
*/
void switchProcessAndLoadURI(in nsIURI aURI);
/**
* Puts the frameloader in prerendering mode.
*/

View File

@ -840,3 +840,5 @@ skip-if = buildapp == 'mulet' || buildapp == 'b2g'
[test_postMessages.html]
support-files = worker_postMessages.js
[test_window_proto.html]
[test_frameLoader_switchProcess.html]
skip-if = e10s || os != 'linux' || buildapp != 'browser'

View File

@ -0,0 +1,84 @@
<!DOCTYPE HTML>
<html>
<head>
<title>Test frameLoader SwitchProcessAndLoadURI</title>
<script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
</head>
<body>
<script type="application/javascript;version=1.7">
SimpleTest.waitForExplicitFinish();
var Ci = SpecialPowers.Ci;
var Cc = SpecialPowers.Cc;
function expectProcessCreated() {
return new Promise((resolve, reject) => {
var topic = "process-priority-manager:TEST-ONLY:process-created";
function observer() {
SpecialPowers.removeObserver(observer, topic);
ok(true, "Expect process created");
resolve();
}
SpecialPowers.addObserver(observer, topic, /* weak = */ false);
});
}
function switchProcessAndLoadURI(iframe, url) {
var fl = SpecialPowers.wrap(iframe)
.QueryInterface(Ci.nsIFrameLoaderOwner)
.frameLoader;
var uri = SpecialPowers.Services.io.newURI(url, null, null);
fl.switchProcessAndLoadURI(uri);
}
var messageManager;
function runTest() {
ok(true, "Run Test");
var iframe = document.createElement("iframe");
iframe.setAttribute("mozbrowser", "true");
iframe.setAttribute("remote", "true");
iframe.setAttribute("src", "http://example.org");
expectProcessCreated()
.then(() => new Promise(next => {
iframe.addEventListener("mozbrowserloadend", function loadend(e) {
iframe.removeEventListener("mozbrowserloadend", loadend);
ok(true, "Got mozbrowserloadend");
mm = SpecialPowers.getBrowserFrameMessageManager(iframe);
expectProcessCreated().then(next);
switchProcessAndLoadURI(iframe, "data:text/html,%3Cscript%3Ealert(true)%3C/script%3E");
});
}))
.then(() => new Promise(next => {
var newMessageManager = SpecialPowers.getBrowserFrameMessageManager(iframe);
isnot(messageManager, newMessageManager, "Got a new message manager");
messageManager = newMessageManager;
iframe.addEventListener("mozbrowsershowmodalprompt", function prompt(e) {
iframe.removeEventListener("mozbrowsershowmodalprompt", prompt);
ok(true, "Browser API still works after process switch");
next();
});
}))
.then(SimpleTest.finish);
document.body.appendChild(iframe);
}
SpecialPowers.pushPrefEnv(
// XXX Set LRUPoolLevels to 2 to avoid breaking priority tests
{ "set": [["dom.ipc.processPriorityManager.BACKGROUND.LRUPoolLevels", 2],
["dom.ipc.processPriorityManager.BACKGROUND_PERCEIVABLE.LRUPoolLevels", 2],
["dom.ipc.processPriorityManager.testMode", true],
["dom.ipc.processPriorityManager.enabled", true],
["dom.ipc.tabs.disabled", false],
["dom.ipc.processCount", 3],
["dom.mozBrowserFramesEnabled", true]] },
() => SpecialPowers.pushPermissions([
{ "type": "browser", "allow": 1, "context": document }
], runTest));
</script>
</body>
</html>

View File

@ -77,6 +77,8 @@ function BrowserElementParent() {
Services.obs.addObserver(this, 'oop-frameloader-crashed', /* ownsWeak = */ true);
Services.obs.addObserver(this, 'copypaste-docommand', /* ownsWeak = */ true);
Services.obs.addObserver(this, 'ask-children-to-execute-copypaste-command', /* ownsWeak = */ true);
Services.obs.addObserver(this, 'frameloader-message-manager-will-change', /* ownsWeak = */ true);
Services.obs.addObserver(this, 'frameloader-message-manager-changed', /* ownsWeak = */ true);
}
BrowserElementParent.prototype = {
@ -161,10 +163,17 @@ BrowserElementParent.prototype = {
_setupMessageListener: function() {
this._mm = this._frameLoader.messageManager;
let self = this;
let isWidget = this._frameLoader
.QueryInterface(Ci.nsIFrameLoader)
.ownerIsWidget;
this._isWidget = this._frameLoader
.QueryInterface(Ci.nsIFrameLoader)
.ownerIsWidget;
this._mm.addMessageListener('browser-element-api:call', this);
this._mm.loadFrameScript("chrome://global/content/extensions.js", true);
},
receiveMessage: function(aMsg) {
if (!this._isAlive()) {
return;
}
// Messages we receive are handed to functions which take a (data) argument,
// where |data| is the message manager's data object.
@ -222,20 +231,15 @@ BrowserElementParent.prototype = {
"opentab": this._fireEventFromMsg
};
this._mm.addMessageListener('browser-element-api:call', function(aMsg) {
if (!self._isAlive()) {
return;
}
if (aMsg.data.msg_name in mmCalls) {
return mmCalls[aMsg.data.msg_name].apply(this, arguments);
} else if (!this._isWidget && aMsg.data.msg_name in mmSecuritySensitiveCalls) {
return mmSecuritySensitiveCalls[aMsg.data.msg_name].apply(this, arguments);
}
},
if (aMsg.data.msg_name in mmCalls) {
return mmCalls[aMsg.data.msg_name].apply(self, arguments);
} else if (!isWidget && aMsg.data.msg_name in mmSecuritySensitiveCalls) {
return mmSecuritySensitiveCalls[aMsg.data.msg_name]
.apply(self, arguments);
}
});
this._mm.loadFrameScript("chrome://global/content/extensions.js", true);
_removeMessageListener: function() {
this._mm.removeMessageListener('browser-element-api:call', this);
},
/**
@ -1105,6 +1109,16 @@ BrowserElementParent.prototype = {
this._sendAsyncMsg('copypaste-do-command', { command: data });
}
break;
case 'frameloader-message-manager-will-change':
if (this._isAlive() && subject == this._frameLoader) {
this._removeMessageListener();
}
break;
case 'frameloader-message-manager-changed':
if (this._isAlive() && subject == this._frameLoader) {
this._setupMessageListener();
}
break;
default:
debug('Unknown topic: ' + topic);
break;

View File

@ -66,10 +66,13 @@ nsBrowserElement::InitBrowserElementAPI()
return;
}
mBrowserElementAPI = do_CreateInstance("@mozilla.org/dom/browser-element-api;1");
if (mBrowserElementAPI) {
mBrowserElementAPI->SetFrameLoader(frameLoader);
if (!mBrowserElementAPI) {
mBrowserElementAPI = do_CreateInstance("@mozilla.org/dom/browser-element-api;1");
if (NS_WARN_IF(!mBrowserElementAPI)) {
return;
}
}
mBrowserElementAPI->SetFrameLoader(frameLoader);
}
void

View File

@ -278,6 +278,7 @@ TabParent::TabParent(nsIContentParent* aManager,
, mManager(aManager)
, mMarkedDestroying(false)
, mIsDestroyed(false)
, mIsDetached(true)
, mAppPackageFileDescriptorSent(false)
, mSendOfflineStatus(true)
, mChromeFlags(aChromeFlags)
@ -481,6 +482,35 @@ TabParent::Destroy()
mMarkedDestroying = true;
}
void
TabParent::Detach()
{
if (mIsDetached) {
return;
}
RemoveWindowListeners();
if (RenderFrameParent* frame = GetRenderFrame()) {
RemoveTabParentFromTable(frame->GetLayersId());
}
mIsDetached = true;
}
void
TabParent::Attach(nsFrameLoader* aFrameLoader)
{
MOZ_ASSERT(mIsDetached);
if (!mIsDetached) {
return;
}
Element* ownerElement = aFrameLoader->GetOwnerContent();
SetOwnerElement(ownerElement);
if (RenderFrameParent* frame = GetRenderFrame()) {
AddTabParentToTable(frame->GetLayersId(), this);
frame->OwnerContentChanged(ownerElement);
}
mIsDetached = false;
}
bool
TabParent::Recv__delete__()
{

View File

@ -122,6 +122,8 @@ public:
nsIXULBrowserWindow* GetXULBrowserWindow();
void Destroy();
void Detach();
void Attach(nsFrameLoader* aFrameLoader);
void RemoveWindowListeners();
void AddWindowListeners();
@ -516,6 +518,8 @@ private:
bool mMarkedDestroying;
// When true, the TabParent is invalid and we should not send IPC messages anymore.
bool mIsDestroyed;
// When true, the TabParent is detached from the frame loader.
bool mIsDetached;
// Whether we have already sent a FileDescriptor for the app package.
bool mAppPackageFileDescriptorSent;