Skip to content

Commit

Permalink
Bug 1739369 - [marionette] Move wait for initial page load logic into…
Browse files Browse the repository at this point in the history
… shared navigate module. r=webdriver-reviewers,jdescottes

Differential Revision: https://phabricator.services.mozilla.com/D134541
  • Loading branch information
whimboo committed Jan 7, 2022
1 parent c8c1a3c commit 0ae2105
Show file tree
Hide file tree
Showing 6 changed files with 191 additions and 48 deletions.
1 change: 1 addition & 0 deletions remote/jar.mn
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ remote.jar:
# shared modules (all protocols)
content/shared/Format.jsm (shared/Format.jsm)
content/shared/Log.jsm (shared/Log.jsm)
content/shared/Navigate.jsm (shared/Navigate.jsm)
content/shared/RecommendedPreferences.jsm (shared/RecommendedPreferences.jsm)
content/shared/Stack.jsm (shared/Stack.jsm)
content/shared/Sync.jsm (shared/Sync.jsm)
Expand Down
43 changes: 3 additions & 40 deletions remote/marionette/driver.js
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,8 @@ XPCOMUtils.defineLazyModuleGetters(this, {
"chrome://remote/content/shared/webdriver/Capabilities.jsm",
unregisterCommandsActor:
"chrome://remote/content/marionette/actors/MarionetteCommandsParent.jsm",
waitForInitialNavigationCompleted:
"chrome://remote/content/shared/Navigate.jsm",
waitForLoadEvent: "chrome://remote/content/marionette/sync.js",
waitForObserverTopic: "chrome://remote/content/marionette/sync.js",
WebDriverSession: "chrome://remote/content/shared/webdriver/Session.jsm",
Expand Down Expand Up @@ -462,46 +464,7 @@ GeckoDriver.prototype.newSession = async function(cmd) {
const browsingContext = this.curBrowser.contentBrowser.browsingContext;
this.currentSession.contentBrowsingContext = browsingContext;

let resolveNavigation;

// Prepare a promise that will resolve upon a navigation.
const onProgressListenerNavigation = new Promise(
resolve => (resolveNavigation = resolve)
);

// Create a basic webprogress listener which will check if the browsing
// context is ready for the new session on every state change.
const navigationListener = {
onStateChange: (progress, request, flag, status) => {
const isStop = flag & Ci.nsIWebProgressListener.STATE_STOP;
if (isStop) {
resolveNavigation();
}
},

QueryInterface: ChromeUtils.generateQI([
"nsIWebProgressListener",
"nsISupportsWeakReference",
]),
};

// Monitor the webprogress listener before checking isLoadingDocument to
// avoid race conditions.
browsingContext.webProgress.addProgressListener(
navigationListener,
Ci.nsIWebProgress.NOTIFY_STATE_WINDOW |
Ci.nsIWebProgress.NOTIFY_STATE_DOCUMENT
);

if (browsingContext.webProgress.isLoadingDocument) {
await onProgressListenerNavigation;
}

browsingContext.webProgress.removeProgressListener(
navigationListener,
Ci.nsIWebProgress.NOTIFY_STATE_WINDOW |
Ci.nsIWebProgress.NOTIFY_STATE_DOCUMENT
);
await waitForInitialNavigationCompleted(browsingContext);

this.curBrowser.contentBrowser.focus();
}
Expand Down
29 changes: 21 additions & 8 deletions remote/marionette/test/xpcshell/test_navigate.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,17 +11,20 @@ const { navigate } = ChromeUtils.import(
"chrome://remote/content/marionette/navigate.js"
);

const topContext = {
const mockTopContext = {
get children() {
return [mockNestedContext];
},
id: 7,
get top() {
return this;
},
};

const nestedContext = {
const mockNestedContext = {
id: 8,
parent: topContext,
top: topContext,
parent: mockTopContext,
top: mockTopContext,
};

add_test(function test_isLoadEventExpectedForCurrent() {
Expand Down Expand Up @@ -66,11 +69,21 @@ add_test(function test_isLoadEventExpectedForTarget() {
const data = [
{ cur: "http://a/", target: "", expected: true },
{ cur: "http://a/", target: "_blank", expected: false },
{ cur: "http://a/", target: "_parent", bc: topContext, expected: true },
{ cur: "http://a/", target: "_parent", bc: nestedContext, expected: false },
{ cur: "http://a/", target: "_parent", bc: mockTopContext, expected: true },
{
cur: "http://a/",
target: "_parent",
bc: mockNestedContext,
expected: false,
},
{ cur: "http://a/", target: "_self", expected: true },
{ cur: "http://a/", target: "_top", bc: topContext, expected: true },
{ cur: "http://a/", target: "_top", bc: nestedContext, expected: false },
{ cur: "http://a/", target: "_top", bc: mockTopContext, expected: true },
{
cur: "http://a/",
target: "_top",
bc: mockNestedContext,
expected: false,
},
];

for (const entry of data) {
Expand Down
78 changes: 78 additions & 0 deletions remote/shared/Navigate.jsm
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
* You can obtain one at http://mozilla.org/MPL/2.0/. */

"use strict";

const EXPORTED_SYMBOLS = ["waitForInitialNavigationCompleted"];

const { XPCOMUtils } = ChromeUtils.import(
"resource://gre/modules/XPCOMUtils.jsm"
);

XPCOMUtils.defineLazyModuleGetters(this, {
Log: "chrome://remote/content/shared/Log.jsm",
});

XPCOMUtils.defineLazyGetter(this, "logger", () =>
Log.get(Log.TYPES.REMOTE_AGENT)
);

// Used to keep weak references of webProgressListeners alive.
const webProgressListeners = new Set();

/**
* Wait until the initial load of the given browsing context is done.
*
* @param {BrowsingContext} browsingContext
* The browsing context to check.
*/
function waitForInitialNavigationCompleted(browsingContext) {
let listener;

return new Promise(resolve => {
listener = new ProgressListener(resolve);
// Keep a reference to the weak listener so it's not getting gc'ed.
webProgressListeners.add(listener);

// Monitor the webprogress listener before checking isLoadingDocument to
// avoid race conditions.
browsingContext.webProgress.addProgressListener(
listener,
Ci.nsIWebProgress.NOTIFY_STATE_WINDOW |
Ci.nsIWebProgress.NOTIFY_STATE_DOCUMENT
);

if (!browsingContext.webProgress.isLoadingDocument) {
logger.trace("Initial navigation already completed");
resolve();
}
}).finally(() => {
browsingContext.webProgress.removeProgressListener(
listener,
Ci.nsIWebProgress.NOTIFY_STATE_WINDOW |
Ci.nsIWebProgress.NOTIFY_STATE_DOCUMENT
);
webProgressListeners.delete(listener);
});
}

class ProgressListener {
constructor(resolve) {
this.resolve = resolve;
}

onStateChange(progress, request, flag, status) {
const isStop = flag & Ci.nsIWebProgressListener.STATE_STOP;
if (isStop) {
this.resolve();
}
}

get QueryInterface() {
return ChromeUtils.generateQI([
"nsIWebProgressListener",
"nsISupportsWeakReference",
]);
}
}
87 changes: 87 additions & 0 deletions remote/shared/test/xpcshell/test_Navigate.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
* You can obtain one at http://mozilla.org/MPL/2.0/. */

const { waitForInitialNavigationCompleted } = ChromeUtils.import(
"chrome://remote/content/shared/Navigate.jsm"
);

const mockWebProgress = {
onStateChangeCalled: false,
isLoadingDocument: false,
addProgressListener(listener) {
if (!this.isLoadingDocument) {
return;
}

listener.onStateChange(
null,
null,
Ci.nsIWebProgressListener.STATE_STOP,
null
);
this.onStateChangeCalled = true;
},
removeProgressListener(listener) {},
};

const mockTopContext = {
get children() {
return [mockNestedContext];
},
id: 7,
get top() {
return this;
},
webProgress: mockWebProgress,
};

const mockNestedContext = {
id: 8,
parent: mockTopContext,
top: mockTopContext,
webProgress: mockWebProgress,
};

add_test(async function test_waitForInitialNavigationCompleted_isNotLoading() {
const browsingContext = Object.assign({}, mockTopContext);
await waitForInitialNavigationCompleted(browsingContext);
ok(
!browsingContext.webProgress.onStateChangeCalled,
"WebProgress' onStateChange hasn't been fired"
);

run_next_test();
});

add_test(async function test_waitForInitialNavigationCompleted_topIsLoading() {
const browsingContext = Object.assign({}, mockTopContext);
browsingContext.webProgress.isLoadingDocument = true;
await waitForInitialNavigationCompleted(browsingContext);
ok(
browsingContext.webProgress.onStateChangeCalled,
"WebProgress' onStateChange has been fired"
);

run_next_test();
});

add_test(
async function test_waitForInitialNavigationCompleted_nestedIsLoading() {
const browsingContext = Object.assign({}, mockNestedContext);
const topContext = browsingContext.top;

browsingContext.webProgress.isLoadingDocument = true;
await waitForInitialNavigationCompleted(topContext);
ok(
browsingContext.webProgress.onStateChangeCalled,
"WebProgress' onStateChange has been fired on the nested browsing context"
);
ok(
topContext.webProgress.onStateChangeCalled,
"WebProgress' onStateChange has been fired on the top browsing context"
);

run_next_test();
}
);
1 change: 1 addition & 0 deletions remote/shared/test/xpcshell/xpcshell.ini
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
# file, You can obtain one at http://mozilla.org/MPL/2.0/.

[test_Format.js]
[test_Navigate.js]
[test_RecommendedPreferences.js]
[test_Stack.js]
[test_Sync.js]

0 comments on commit 0ae2105

Please sign in to comment.