mirror of
https://gitlab.winehq.org/wine/wine-gecko.git
synced 2024-09-13 09:24:08 -07:00
Bug 1096488 - Detect and handle switching from remote to non-remote pages and back in marionette.;r=automatedtester
This commit is contained in:
parent
b6bcb7bcea
commit
744c5468c0
@ -208,6 +208,7 @@ FrameManager.prototype = {
|
||||
messageManager.addWeakMessageListener("Marionette:addCookie", this.server);
|
||||
messageManager.addWeakMessageListener("Marionette:getVisibleCookies", this.server);
|
||||
messageManager.addWeakMessageListener("Marionette:deleteCookie", this.server);
|
||||
messageManager.addWeakMessageListener("Marionette:listenersAttached", this.server);
|
||||
messageManager.addWeakMessageListener("MarionetteFrame:handleModal", this);
|
||||
messageManager.addWeakMessageListener("MarionetteFrame:getCurrentFrameId", this);
|
||||
messageManager.addWeakMessageListener("MarionetteFrame:getInterruptedState", this);
|
||||
@ -239,8 +240,8 @@ FrameManager.prototype = {
|
||||
messageManager.removeWeakMessageListener("Marionette:addCookie", this.server);
|
||||
messageManager.removeWeakMessageListener("Marionette:getVisibleCookies", this.server);
|
||||
messageManager.removeWeakMessageListener("Marionette:deleteCookie", this.server);
|
||||
messageManager.removeWeakMessageListener("Marionette:listenersAttached", this.server);
|
||||
messageManager.removeWeakMessageListener("MarionetteFrame:handleModal", this);
|
||||
messageManager.removeWeakMessageListener("MarionetteFrame:getCurrentFrameId", this);
|
||||
},
|
||||
|
||||
};
|
||||
|
@ -63,6 +63,9 @@ let originalOnError;
|
||||
let checkTimer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
|
||||
//timer for readystate
|
||||
let readyStateTimer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
|
||||
// timer for navigation commands.
|
||||
let navTimer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
|
||||
let onDOMContentLoaded;
|
||||
// Send move events about this often
|
||||
let EVENT_INTERVAL = 30; // milliseconds
|
||||
// For assigning unique ids to all touches
|
||||
@ -97,13 +100,14 @@ let modalHandler = function() {
|
||||
* If the actor returns an ID, we start the listeners. Otherwise, nothing happens.
|
||||
*/
|
||||
function registerSelf() {
|
||||
let msg = {value: winUtil.outerWindowID, href: content.location.href};
|
||||
let msg = {value: winUtil.outerWindowID}
|
||||
// register will have the ID and a boolean describing if this is the main process or not
|
||||
let register = sendSyncMessage("Marionette:register", msg);
|
||||
|
||||
if (register[0]) {
|
||||
listenerId = register[0][0].id;
|
||||
if (typeof listenerId != "undefined") {
|
||||
let {id, remotenessChange} = register[0][0];
|
||||
listenerId = id;
|
||||
if (typeof id != "undefined") {
|
||||
// check if we're the main process
|
||||
if (register[0][1] == true) {
|
||||
addMessageListener("MarionetteMainListener:emitTouchEvent", emitTouchEventForIFrame);
|
||||
@ -111,6 +115,9 @@ function registerSelf() {
|
||||
importedScripts = FileUtils.getDir('TmpD', [], false);
|
||||
importedScripts.append('marionetteContentScripts');
|
||||
startListeners();
|
||||
if (remotenessChange) {
|
||||
sendAsyncMessage("Marionette:listenersAttached", {listenerId: id});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -170,6 +177,8 @@ function startListeners() {
|
||||
addMessageListenerId("Marionette:actionChain", actionChain);
|
||||
addMessageListenerId("Marionette:multiAction", multiAction);
|
||||
addMessageListenerId("Marionette:get", get);
|
||||
addMessageListenerId("Marionette:pollForReadyState", pollForReadyState);
|
||||
addMessageListenerId("Marionette:cancelRequest", cancelRequest);
|
||||
addMessageListenerId("Marionette:getCurrentUrl", getCurrentUrl);
|
||||
addMessageListenerId("Marionette:getTitle", getTitle);
|
||||
addMessageListenerId("Marionette:getPageSource", getPageSource);
|
||||
@ -273,6 +282,8 @@ function deleteSession(msg) {
|
||||
removeMessageListenerId("Marionette:actionChain", actionChain);
|
||||
removeMessageListenerId("Marionette:multiAction", multiAction);
|
||||
removeMessageListenerId("Marionette:get", get);
|
||||
removeMessageListenerId("Marionette:pollForReadyState", pollForReadyState);
|
||||
removeMessageListenerId("Marionette:cancelRequest", cancelRequest);
|
||||
removeMessageListenerId("Marionette:getTitle", getTitle);
|
||||
removeMessageListenerId("Marionette:getPageSource", getPageSource);
|
||||
removeMessageListenerId("Marionette:getCurrentUrl", getCurrentUrl);
|
||||
@ -1404,6 +1415,52 @@ function multiAction(msg) {
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* This implements the latter part of a get request (for the case we need to resume one
|
||||
* when a remoteness update happens in the middle of a navigate request). This is most of
|
||||
* of the work of a navigate request, but doesn't assume DOMContentLoaded is yet to fire.
|
||||
*/
|
||||
function pollForReadyState(msg, start, callback) {
|
||||
let {pageTimeout, url, command_id} = msg.json;
|
||||
start = start ? start : new Date().getTime();
|
||||
|
||||
if (!callback) {
|
||||
callback = () => {};
|
||||
}
|
||||
|
||||
let end = null;
|
||||
function checkLoad() {
|
||||
navTimer.cancel();
|
||||
end = new Date().getTime();
|
||||
let aboutErrorRegex = /about:.+(error)\?/;
|
||||
let elapse = end - start;
|
||||
if (pageTimeout == null || elapse <= pageTimeout) {
|
||||
if (curFrame.document.readyState == "complete") {
|
||||
callback();
|
||||
sendOk(command_id);
|
||||
} else if (curFrame.document.readyState == "interactive" &&
|
||||
aboutErrorRegex.exec(curFrame.document.baseURI) &&
|
||||
!curFrame.document.baseURI.startsWith(url)) {
|
||||
// We have reached an error url without requesting it.
|
||||
callback();
|
||||
sendError("Error loading page", 13, null, command_id);
|
||||
} else if (curFrame.document.readyState == "interactive" &&
|
||||
curFrame.document.baseURI.startsWith("about:")) {
|
||||
callback();
|
||||
sendOk(command_id);
|
||||
} else {
|
||||
navTimer.initWithCallback(checkLoad, 100, Ci.nsITimer.TYPE_ONE_SHOT);
|
||||
}
|
||||
}
|
||||
else {
|
||||
callback();
|
||||
sendError("Error loading page, timed out (checkLoad)", 21, null,
|
||||
command_id);
|
||||
}
|
||||
}
|
||||
checkLoad();
|
||||
}
|
||||
|
||||
/**
|
||||
* Navigate to the given URL. The operation will be performed on the
|
||||
* current browser context, and handles the case where we navigate
|
||||
@ -1411,67 +1468,56 @@ function multiAction(msg) {
|
||||
* (in chrome space).
|
||||
*/
|
||||
function get(msg) {
|
||||
let command_id = msg.json.command_id;
|
||||
|
||||
let checkTimer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
|
||||
let start = new Date().getTime();
|
||||
let end = null;
|
||||
function checkLoad() {
|
||||
checkTimer.cancel();
|
||||
end = new Date().getTime();
|
||||
let aboutErrorRegex = /about:.+(error)\?/;
|
||||
let elapse = end - start;
|
||||
if (msg.json.pageTimeout == null || elapse <= msg.json.pageTimeout) {
|
||||
if (curFrame.document.readyState == "complete") {
|
||||
removeEventListener("DOMContentLoaded", onDOMContentLoaded, false);
|
||||
sendOk(command_id);
|
||||
} else if (curFrame.document.readyState == "interactive" &&
|
||||
aboutErrorRegex.exec(curFrame.document.baseURI) &&
|
||||
!curFrame.document.baseURI.startsWith(msg.json.url)) {
|
||||
// We have reached an error url without requesting it.
|
||||
removeEventListener("DOMContentLoaded", onDOMContentLoaded, false);
|
||||
sendError("Error loading page", 13, null, command_id);
|
||||
} else if (curFrame.document.readyState == "interactive" &&
|
||||
curFrame.document.baseURI.startsWith("about:")) {
|
||||
removeEventListener("DOMContentLoaded", onDOMContentLoaded, false);
|
||||
sendOk(command_id);
|
||||
} else {
|
||||
checkTimer.initWithCallback(checkLoad, 100, Ci.nsITimer.TYPE_ONE_SHOT);
|
||||
}
|
||||
}
|
||||
else {
|
||||
removeEventListener("DOMContentLoaded", onDOMContentLoaded, false);
|
||||
sendError("Error loading page, timed out (checkLoad)", 21, null,
|
||||
command_id);
|
||||
}
|
||||
}
|
||||
|
||||
// Prevent DOMContentLoaded events from frames from invoking this
|
||||
// code, unless the event is coming from the frame associated with
|
||||
// the current window (i.e. someone has used switch_to_frame).
|
||||
let onDOMContentLoaded = function onDOMContentLoaded(event) {
|
||||
onDOMContentLoaded = function onDOMContentLoaded(event) {
|
||||
if (!event.originalTarget.defaultView.frameElement ||
|
||||
event.originalTarget.defaultView.frameElement == curFrame.frameElement) {
|
||||
checkLoad();
|
||||
pollForReadyState(msg, start, () => {
|
||||
removeEventListener("DOMContentLoaded", onDOMContentLoaded, false);
|
||||
onDOMContentLoaded = null;
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
function timerFunc() {
|
||||
removeEventListener("DOMContentLoaded", onDOMContentLoaded, false);
|
||||
sendError("Error loading page, timed out (onDOMContentLoaded)", 21,
|
||||
null, command_id);
|
||||
null, msg.json.command_id);
|
||||
}
|
||||
if (msg.json.pageTimeout != null) {
|
||||
checkTimer.initWithCallback(timerFunc, msg.json.pageTimeout, Ci.nsITimer.TYPE_ONE_SHOT);
|
||||
navTimer.initWithCallback(timerFunc, msg.json.pageTimeout, Ci.nsITimer.TYPE_ONE_SHOT);
|
||||
}
|
||||
addEventListener("DOMContentLoaded", onDOMContentLoaded, false);
|
||||
curFrame.location = msg.json.url;
|
||||
}
|
||||
|
||||
/**
|
||||
* Cancel the polling and remove the event listener associated with a current
|
||||
* navigation request in case we're interupted by an onbeforeunload handler
|
||||
* and navigation doesn't complete.
|
||||
*/
|
||||
function cancelRequest() {
|
||||
navTimer.cancel();
|
||||
if (onDOMContentLoaded) {
|
||||
removeEventListener("DOMContentLoaded", onDOMContentLoaded, false);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get URL of the top level browsing context.
|
||||
*/
|
||||
function getCurrentUrl(msg) {
|
||||
sendResponse({value: curFrame.location.href}, msg.json.command_id);
|
||||
let url;
|
||||
if (msg.json.isB2G) {
|
||||
url = curFrame.location.href;
|
||||
} else {
|
||||
url = content.location.href;
|
||||
}
|
||||
sendResponse({value: url}, msg.json.command_id);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -288,8 +288,10 @@ MarionetteServerConnection.prototype = {
|
||||
}
|
||||
}
|
||||
else {
|
||||
this.messageManager.broadcastAsyncMessage(
|
||||
"Marionette:" + name + this.curBrowser.curFrameId, values);
|
||||
this.curBrowser.executeWhenReady(() => {
|
||||
this.messageManager.broadcastAsyncMessage(
|
||||
"Marionette:" + name + this.curBrowser.curFrameId, values);
|
||||
});
|
||||
}
|
||||
return success;
|
||||
},
|
||||
@ -329,6 +331,11 @@ MarionetteServerConnection.prototype = {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (this.curBrowser !== null) {
|
||||
this.curBrowser.pendingCommands = [];
|
||||
}
|
||||
|
||||
this.conn.send(msg);
|
||||
if (command_id != -1) {
|
||||
// Don't unset this.command_id if this message is to process an
|
||||
@ -1278,7 +1285,17 @@ MarionetteServerConnection.prototype = {
|
||||
*/
|
||||
get: function MDA_get(aRequest) {
|
||||
let command_id = this.command_id = this.getCommandId();
|
||||
|
||||
if (this.context != "chrome") {
|
||||
// If a remoteness update interrupts our page load, this will never return
|
||||
// We need to re-issue this request to correctly poll for readyState and
|
||||
// send errors.
|
||||
this.curBrowser.pendingCommands.push(() => {
|
||||
aRequest.parameters.command_id = command_id;
|
||||
this.messageManager.broadcastAsyncMessage(
|
||||
"Marionette:pollForReadyState" + this.curBrowser.curFrameId,
|
||||
aRequest.parameters);
|
||||
});
|
||||
aRequest.command_id = command_id;
|
||||
aRequest.parameters.pageTimeout = this.pageTimeout;
|
||||
this.sendAsync("get", aRequest.parameters, command_id);
|
||||
@ -1337,15 +1354,7 @@ MarionetteServerConnection.prototype = {
|
||||
this.sendResponse(this.getCurrentWindow().location.href, this.command_id);
|
||||
}
|
||||
else {
|
||||
if (isB2G) {
|
||||
this.sendAsync("getCurrentUrl", {}, this.command_id);
|
||||
}
|
||||
else {
|
||||
this.sendResponse(this.curBrowser
|
||||
.tab
|
||||
.linkedBrowser
|
||||
.contentWindowAsCPOW.location.href, this.command_id);
|
||||
}
|
||||
this.sendAsync("getCurrentUrl", {isB2G: isB2G}, this.command_id);
|
||||
}
|
||||
},
|
||||
|
||||
@ -1439,6 +1448,13 @@ MarionetteServerConnection.prototype = {
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Forces an update for the given browser's id.
|
||||
*/
|
||||
updateIdForBrowser: function (browser, newId) {
|
||||
this._browserIds.set(browser.permanentKey, newId);
|
||||
},
|
||||
|
||||
/**
|
||||
* Retrieves a listener id for the given xul browser element. In case
|
||||
* the browser is not known, an attempt is made to retrieve the id from
|
||||
@ -1601,8 +1617,7 @@ MarionetteServerConnection.prototype = {
|
||||
this.curBrowser = this.browsers[outerId];
|
||||
if (contentWindowId) {
|
||||
// The updated id corresponds to switching to a new tab.
|
||||
this.curBrowser.curFrameId = contentWindowId;
|
||||
win.gBrowser.selectTabAtIndex(ind);
|
||||
this.curBrowser.switchToTab(ind);
|
||||
}
|
||||
this.sendOk(command_id);
|
||||
}
|
||||
@ -3072,6 +3087,7 @@ MarionetteServerConnection.prototype = {
|
||||
}
|
||||
|
||||
if (this.command_id) {
|
||||
this.sendAsync("cancelRequest", {});
|
||||
// This is a shortcut to get the client to accept our response whether
|
||||
// the expected key is 'ok' (in case a click or similar got us here)
|
||||
// or 'value' (in case an execute script or similar got us here).
|
||||
@ -3214,8 +3230,9 @@ MarionetteServerConnection.prototype = {
|
||||
let mainContent = (this.curBrowser.mainContentId == null);
|
||||
if (!browserType || browserType != "content") {
|
||||
//curBrowser holds all the registered frames in knownFrames
|
||||
let listenerId = this.generateFrameId(message.json.value);
|
||||
reg.id = this.curBrowser.register(listenerId);
|
||||
let uid = this.generateFrameId(message.json.value);
|
||||
reg.id = uid;
|
||||
reg.remotenessChange = this.curBrowser.register(uid, message.target);
|
||||
}
|
||||
// set to true if we updated mainContentId
|
||||
mainContent = ((mainContent == true) && (this.curBrowser.mainContentId != null));
|
||||
@ -3253,6 +3270,19 @@ MarionetteServerConnection.prototype = {
|
||||
globalMessageManager.broadcastAsyncMessage(
|
||||
"MarionetteMainListener:emitTouchEvent", message.json);
|
||||
return;
|
||||
case "Marionette:listenersAttached":
|
||||
if (message.json.listenerId === this.curBrowser.curFrameId) {
|
||||
// If remoteness gets updated we need to call newSession. In the case
|
||||
// of desktop this just sets up a small amount of state that doesn't
|
||||
// change over the course of a session.
|
||||
let newSessionValues = {
|
||||
B2G: (appName == "B2G"),
|
||||
raisesAccessibilityExceptions: this.sessionCapabilities.raisesAccessibilityExceptions
|
||||
};
|
||||
this.sendAsync("newSession", newSessionValues);
|
||||
this.curBrowser.flushPendingCommands();
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
};
|
||||
@ -3370,15 +3400,49 @@ function BrowserObj(win, server) {
|
||||
this.setBrowser(win);
|
||||
this.frameManager = new FrameManager(server); //We should have one FM per BO so that we can handle modals in each Browser
|
||||
|
||||
// A reference to the tab corresponding to the current window handle, if any.
|
||||
this.tab = null;
|
||||
this.pendingCommands = [];
|
||||
|
||||
//register all message listeners
|
||||
this.frameManager.addMessageManagerListeners(server.messageManager);
|
||||
this.getIdForBrowser = server.getIdForBrowser.bind(server);
|
||||
this.updateIdForBrowser = server.updateIdForBrowser.bind(server);
|
||||
this._curFrameId = null;
|
||||
this._browserWasRemote = null;
|
||||
this._hasRemotenessChange = false;
|
||||
}
|
||||
|
||||
BrowserObj.prototype = {
|
||||
get tab () {
|
||||
// A reference to the currently selected tab, if any
|
||||
return this.browser ? this.browser.selectedTab : null;
|
||||
|
||||
/**
|
||||
* This function intercepts commands interacting with content and queues
|
||||
* or executes them as needed.
|
||||
*
|
||||
* No commands interacting with content are safe to process until
|
||||
* the new listener script is loaded and registers itself.
|
||||
* This occurs when a command whose effect is asynchronous (such
|
||||
* as goBack) results in a remoteness change and new commands
|
||||
* are subsequently posted to the server.
|
||||
*/
|
||||
executeWhenReady: function (callback) {
|
||||
if (this.hasRemotenessChange()) {
|
||||
this.pendingCommands.push(callback);
|
||||
} else {
|
||||
callback();
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Re-sets this BrowserObject's current tab and updates remoteness tracking.
|
||||
*/
|
||||
switchToTab: function (ind) {
|
||||
if (this.browser) {
|
||||
this.browser.selectTabAtIndex(ind);
|
||||
this.tab = this.browser.selectedTab;
|
||||
}
|
||||
this._browserWasRemote = this.browser.getBrowserForTab(this.tab).isRemoteBrowser;
|
||||
this._hasRemotenessChange = false;
|
||||
},
|
||||
|
||||
/**
|
||||
@ -3418,15 +3482,31 @@ BrowserObj.prototype = {
|
||||
break;
|
||||
}
|
||||
},
|
||||
|
||||
// The current frame id is managed per browser element on desktop in case
|
||||
// the id needs to be refreshed. The currently selected window is identified
|
||||
// within BrowserObject by a tab.
|
||||
get curFrameId () {
|
||||
if (appName != "Firefox") {
|
||||
return this._curFrameId;
|
||||
}
|
||||
if (this.tab) {
|
||||
let browser = this.browser.getBrowserForTab(this.tab);
|
||||
return this.getIdForBrowser(browser);
|
||||
}
|
||||
return null;
|
||||
},
|
||||
|
||||
set curFrameId (id) {
|
||||
if (appName != "Firefox") {
|
||||
this._curFrameId = id;
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Called when we start a session with this browser.
|
||||
*/
|
||||
startSession: function BO_startSession(newSession, win, callback) {
|
||||
if (appName == "Firefox" &&
|
||||
win.gMultiProcessBrowser &&
|
||||
!win.gBrowser.selectedBrowser.isRemoteBrowser) {
|
||||
win.XULBrowserWindow.forceInitialBrowserRemote();
|
||||
}
|
||||
callback(win, newSession);
|
||||
},
|
||||
|
||||
@ -3458,28 +3538,69 @@ BrowserObj.prototype = {
|
||||
*
|
||||
* @param string uid
|
||||
* frame uid for use by marionette
|
||||
* @param the XUL <browser> that was the target of the originating message.
|
||||
*/
|
||||
register: function BO_register(uid) {
|
||||
if (this.curFrameId == null) {
|
||||
let currWinId = null;
|
||||
register: function BO_register(uid, target) {
|
||||
let remotenessChange = this.hasRemotenessChange();
|
||||
if (this.curFrameId === null || remotenessChange) {
|
||||
if (this.browser) {
|
||||
// If we're setting up a new session on Firefox, we only process the
|
||||
// registration for this frame if it belongs to the tab we've just
|
||||
// created.
|
||||
// registration for this frame if it belongs to the current tab.
|
||||
if (!this.tab) {
|
||||
this.switchToTab(this.browser.selectedIndex);
|
||||
}
|
||||
|
||||
let browser = this.browser.getBrowserForTab(this.tab);
|
||||
currWinId = this.getIdForBrowser(browser);
|
||||
}
|
||||
if ((!this.newSession) ||
|
||||
(this.newSession &&
|
||||
((appName != "Firefox") ||
|
||||
uid === currWinId))) {
|
||||
this.curFrameId = uid;
|
||||
if (target == browser) {
|
||||
this.updateIdForBrowser(browser, uid);
|
||||
this.mainContentId = uid;
|
||||
}
|
||||
} else {
|
||||
this._curFrameId = uid;
|
||||
this.mainContentId = uid;
|
||||
}
|
||||
}
|
||||
|
||||
this.knownFrames.push(uid); //used to delete sessions
|
||||
return uid;
|
||||
return remotenessChange;
|
||||
},
|
||||
|
||||
/**
|
||||
* When navigating between pages results in changing a browser's process, we
|
||||
* need to take measures not to lose contact with a listener script. This
|
||||
* function does the necessary bookkeeping.
|
||||
*/
|
||||
hasRemotenessChange: function () {
|
||||
// None of these checks are relevant on b2g or if we don't have a tab yet,
|
||||
// and may not apply on Fennec.
|
||||
if (appName != "Firefox" || this.tab === null) {
|
||||
return false;
|
||||
}
|
||||
if (this._hasRemotenessChange) {
|
||||
return true;
|
||||
}
|
||||
let currentIsRemote = this.browser.getBrowserForTab(this.tab).isRemoteBrowser;
|
||||
this._hasRemotenessChange = this._browserWasRemote !== currentIsRemote;
|
||||
this._browserWasRemote = currentIsRemote;
|
||||
return this._hasRemotenessChange;
|
||||
},
|
||||
|
||||
/**
|
||||
* Flushes any pending commands queued when a remoteness change is being
|
||||
* processed and mark this remotenessUpdate as complete.
|
||||
*/
|
||||
flushPendingCommands: function () {
|
||||
if (!this._hasRemotenessChange) {
|
||||
return;
|
||||
}
|
||||
|
||||
this._hasRemotenessChange = false;
|
||||
this.pendingCommands.forEach((callback) => {
|
||||
callback();
|
||||
});
|
||||
this.pendingCommands = [];
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
|
Loading…
Reference in New Issue
Block a user