mirror of
https://gitlab.winehq.org/wine/wine-gecko.git
synced 2024-09-13 09:24:08 -07:00
Bug 1171256 - Add an API similar to Chrome's webNavigation (r=Mossop)
This commit is contained in:
parent
4decb9f9e6
commit
f7a876cfcc
157
toolkit/modules/addons/WebNavigation.jsm
Normal file
157
toolkit/modules/addons/WebNavigation.jsm
Normal file
@ -0,0 +1,157 @@
|
||||
/* 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 = ["WebNavigation"];
|
||||
|
||||
const Ci = Components.interfaces;
|
||||
const Cc = Components.classes;
|
||||
const Cu = Components.utils;
|
||||
|
||||
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
|
||||
Cu.import("resource://gre/modules/Services.jsm");
|
||||
|
||||
// TODO:
|
||||
// Transition types and qualifiers
|
||||
// onReferenceFragmentUpdated also triggers for pushState
|
||||
// getFrames, getAllFrames
|
||||
// onCreatedNavigationTarget, onHistoryStateUpdated
|
||||
|
||||
let Manager = {
|
||||
listeners: new Map(),
|
||||
|
||||
init() {
|
||||
Services.mm.addMessageListener("Extension:DOMContentLoaded", this);
|
||||
Services.mm.addMessageListener("Extension:StateChange", this);
|
||||
Services.mm.addMessageListener("Extension:LocationChange", this);
|
||||
Services.mm.loadFrameScript("resource://gre/modules/WebNavigationContent.js", true);
|
||||
},
|
||||
|
||||
uninit() {
|
||||
Services.mm.removeMessageListener("Extension:StateChange", this);
|
||||
Services.mm.removeMessageListener("Extension:LocationChange", this);
|
||||
Services.mm.removeMessageListener("Extension:DOMContentLoaded", this);
|
||||
Services.mm.removeDelayedFrameScript("resource://gre/modules/WebNavigationContent.js");
|
||||
Services.mm.broadcastAsyncMessage("Extension:DisableWebNavigation");
|
||||
},
|
||||
|
||||
addListener(type, listener) {
|
||||
if (this.listeners.size == 0) {
|
||||
this.init();
|
||||
}
|
||||
|
||||
if (!this.listeners.has(type)) {
|
||||
this.listeners.set(type, new Set());
|
||||
}
|
||||
let listeners = this.listeners.get(type);
|
||||
listeners.add(listener);
|
||||
},
|
||||
|
||||
removeListener(type, listener) {
|
||||
let listeners = this.listeners.get(type);
|
||||
if (!listeners) {
|
||||
return;
|
||||
}
|
||||
listeners.delete(listener);
|
||||
if (listeners.size == 0) {
|
||||
this.listeners.delete(type);
|
||||
}
|
||||
|
||||
if (this.listeners.size == 0) {
|
||||
this.uninit();
|
||||
}
|
||||
},
|
||||
|
||||
receiveMessage({name, data, target}) {
|
||||
switch (name) {
|
||||
case "Extension:StateChange":
|
||||
this.onStateChange(target, data);
|
||||
break;
|
||||
|
||||
case "Extension:LocationChange":
|
||||
this.onLocationChange(target, data);
|
||||
break;
|
||||
|
||||
case "Extension:DOMContentLoaded":
|
||||
this.onLoad(target, data);
|
||||
break;
|
||||
}
|
||||
},
|
||||
|
||||
onStateChange(browser, data) {
|
||||
let stateFlags = data.stateFlags;
|
||||
if (stateFlags & Ci.nsIWebProgressListener.STATE_IS_WINDOW) {
|
||||
let url = data.requestURL;
|
||||
if (stateFlags & Ci.nsIWebProgressListener.STATE_START) {
|
||||
this.fire("onBeforeNavigate", browser, data, {url});
|
||||
} else if (stateFlags & Ci.nsIWebProgressListener.STATE_STOP) {
|
||||
if (Components.isSuccessCode(data.status)) {
|
||||
this.fire("onCompleted", browser, data, {url});
|
||||
} else {
|
||||
let error = `Error code ${data.status}`;
|
||||
this.fire("onErrorOccurred", browser, data, {error, url});
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
onLocationChange(browser, data) {
|
||||
let url = data.location;
|
||||
if (data.flags & Ci.nsIWebProgressListener.LOCATION_CHANGE_SAME_DOCUMENT) {
|
||||
this.fire("onReferenceFragmentUpdated", browser, data, {url});
|
||||
} else {
|
||||
this.fire("onCommitted", browser, data, {url});
|
||||
}
|
||||
},
|
||||
|
||||
onLoad(browser, data) {
|
||||
this.fire("onDOMContentLoaded", browser, data, {url: data.url});
|
||||
},
|
||||
|
||||
fire(type, browser, data, extra) {
|
||||
let listeners = this.listeners.get(type);
|
||||
if (!listeners) {
|
||||
return;
|
||||
}
|
||||
|
||||
let details = {
|
||||
browser,
|
||||
windowId: data.windowId,
|
||||
};
|
||||
|
||||
if (data.parentWindowId) {
|
||||
details.parentWindowId = data.parentWindowId;
|
||||
}
|
||||
|
||||
for (let prop in extra) {
|
||||
details[prop] = extra[prop];
|
||||
}
|
||||
|
||||
for (let listener of listeners) {
|
||||
listener(details);
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
const EVENTS = [
|
||||
"onBeforeNavigate",
|
||||
"onCommitted",
|
||||
"onDOMContentLoaded",
|
||||
"onCompleted",
|
||||
"onErrorOccurred",
|
||||
"onReferenceFragmentUpdated",
|
||||
|
||||
//"onCreatedNavigationTarget",
|
||||
//"onHistoryStateUpdated",
|
||||
];
|
||||
|
||||
let WebNavigation = {};
|
||||
|
||||
for (let event of EVENTS) {
|
||||
WebNavigation[event] = {
|
||||
addListener: Manager.addListener.bind(Manager, event),
|
||||
removeListener: Manager.removeListener.bind(Manager, event),
|
||||
}
|
||||
}
|
105
toolkit/modules/addons/WebNavigationContent.js
Normal file
105
toolkit/modules/addons/WebNavigationContent.js
Normal file
@ -0,0 +1,105 @@
|
||||
const Ci = Components.interfaces;
|
||||
|
||||
function getWindowId(window)
|
||||
{
|
||||
return window.QueryInterface(Ci.nsIInterfaceRequestor)
|
||||
.getInterface(Ci.nsIDOMWindowUtils)
|
||||
.outerWindowID;
|
||||
}
|
||||
|
||||
function getParentWindowId(window)
|
||||
{
|
||||
return getWindowId(window.parent);
|
||||
}
|
||||
|
||||
function loadListener(event)
|
||||
{
|
||||
let document = event.target;
|
||||
let window = document.defaultView;
|
||||
let url = document.documentURI;
|
||||
let windowId = getWindowId(window);
|
||||
let parentWindowId = getParentWindowId(window);
|
||||
sendAsyncMessage("Extension:DOMContentLoaded", {windowId, parentWindowId, url});
|
||||
}
|
||||
|
||||
addEventListener("DOMContentLoaded", loadListener);
|
||||
addMessageListener("Extension:DisableWebNavigation", () => {
|
||||
removeEventListener("DOMContentLoaded", loadListener);
|
||||
});
|
||||
|
||||
let WebProgressListener = {
|
||||
init: function() {
|
||||
let webProgress = docShell.QueryInterface(Ci.nsIInterfaceRequestor)
|
||||
.getInterface(Ci.nsIWebProgress);
|
||||
webProgress.addProgressListener(this, Ci.nsIWebProgress.NOTIFY_STATE_WINDOW |
|
||||
Ci.nsIWebProgress.NOTIFY_LOCATION);
|
||||
},
|
||||
|
||||
uninit() {
|
||||
if (!docShell) {
|
||||
return;
|
||||
}
|
||||
let webProgress = docShell.QueryInterface(Ci.nsIInterfaceRequestor)
|
||||
.getInterface(Ci.nsIWebProgress);
|
||||
webProgress.removeProgressListener(this);
|
||||
},
|
||||
|
||||
onStateChange: function onStateChange(webProgress, request, stateFlags, status) {
|
||||
let data = {
|
||||
requestURL: request.QueryInterface(Ci.nsIChannel).URI.spec,
|
||||
windowId: webProgress.DOMWindowID,
|
||||
parentWindowId: getParentWindowId(webProgress.DOMWindow),
|
||||
status,
|
||||
stateFlags,
|
||||
};
|
||||
sendAsyncMessage("Extension:StateChange", data);
|
||||
|
||||
if (webProgress.DOMWindow.top != webProgress.DOMWindow) {
|
||||
let webNav = webProgress.QueryInterface(Ci.nsIWebNavigation);
|
||||
if (!webNav.canGoBack) {
|
||||
// For some reason we don't fire onLocationChange for the
|
||||
// initial navigation of a sub-frame. So we need to simulate
|
||||
// it here.
|
||||
let data = {
|
||||
location: request.QueryInterface(Ci.nsIChannel).URI.spec,
|
||||
windowId: webProgress.DOMWindowID,
|
||||
parentWindowId: getParentWindowId(webProgress.DOMWindow),
|
||||
flags: 0,
|
||||
};
|
||||
sendAsyncMessage("Extension:LocationChange", data);
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
onLocationChange: function onLocationChange(webProgress, request, locationURI, flags) {
|
||||
let data = {
|
||||
location: locationURI ? locationURI.spec : "",
|
||||
windowId: webProgress.DOMWindowID,
|
||||
parentWindowId: getParentWindowId(webProgress.DOMWindow),
|
||||
flags,
|
||||
};
|
||||
sendAsyncMessage("Extension:LocationChange", data);
|
||||
},
|
||||
|
||||
QueryInterface: function QueryInterface(aIID) {
|
||||
if (aIID.equals(Ci.nsIWebProgressListener) ||
|
||||
aIID.equals(Ci.nsISupportsWeakReference) ||
|
||||
aIID.equals(Ci.nsISupports)) {
|
||||
return this;
|
||||
}
|
||||
|
||||
throw Components.results.NS_ERROR_NO_INTERFACE;
|
||||
}
|
||||
};
|
||||
|
||||
let disabled = false;
|
||||
WebProgressListener.init();
|
||||
addEventListener("unload", () => {
|
||||
if (!disabled) {
|
||||
WebProgressListener.uninit();
|
||||
}
|
||||
});
|
||||
addMessageListener("Extension:DisableWebNavigation", () => {
|
||||
disabled = true;
|
||||
WebProgressListener.uninit();
|
||||
});
|
@ -13,6 +13,8 @@ SPHINX_TREES['toolkit_modules'] = 'docs'
|
||||
|
||||
EXTRA_JS_MODULES += [
|
||||
'addons/MatchPattern.jsm',
|
||||
'addons/WebNavigation.jsm',
|
||||
'addons/WebNavigationContent.js',
|
||||
'addons/WebRequest.jsm',
|
||||
'addons/WebRequestCommon.jsm',
|
||||
'addons/WebRequestContent.js',
|
||||
|
@ -3,6 +3,9 @@ support-files =
|
||||
dummy_page.html
|
||||
metadata_*.html
|
||||
testremotepagemanager.html
|
||||
file_WebNavigation_page1.html
|
||||
file_WebNavigation_page2.html
|
||||
file_WebNavigation_page3.html
|
||||
file_WebRequest_page1.html
|
||||
file_WebRequest_page2.html
|
||||
file_image_good.png
|
||||
@ -23,6 +26,7 @@ support-files =
|
||||
skip-if = e10s # Bug ?????? - test already uses content scripts, but still fails only under e10s.
|
||||
[browser_Geometry.js]
|
||||
[browser_InlineSpellChecker.js]
|
||||
[browser_WebNavigation.js]
|
||||
[browser_WebRequest.js]
|
||||
[browser_WebRequest_cookies.js]
|
||||
[browser_WebRequest_filtering.js]
|
||||
|
140
toolkit/modules/tests/browser/browser_WebNavigation.js
Normal file
140
toolkit/modules/tests/browser/browser_WebNavigation.js
Normal file
@ -0,0 +1,140 @@
|
||||
"use strict";
|
||||
|
||||
const { interfaces: Ci, classes: Cc, utils: Cu, results: Cr } = Components;
|
||||
|
||||
let {WebNavigation} = Cu.import("resource://gre/modules/WebNavigation.jsm", {});
|
||||
|
||||
const BASE = "http://example.com/browser/toolkit/modules/tests/browser";
|
||||
const URL = BASE + "/file_WebNavigation_page1.html";
|
||||
const FRAME = BASE + "/file_WebNavigation_page2.html";
|
||||
const FRAME2 = BASE + "/file_WebNavigation_page3.html";
|
||||
|
||||
const EVENTS = [
|
||||
"onBeforeNavigate",
|
||||
"onCommitted",
|
||||
"onDOMContentLoaded",
|
||||
"onCompleted",
|
||||
"onErrorOccurred",
|
||||
"onReferenceFragmentUpdated",
|
||||
];
|
||||
|
||||
const REQUIRED = [
|
||||
"onBeforeNavigate",
|
||||
"onCommitted",
|
||||
"onDOMContentLoaded",
|
||||
"onCompleted",
|
||||
];
|
||||
|
||||
let expectedBrowser;
|
||||
let received = [];
|
||||
let completedResolve;
|
||||
let waitingURL, waitingEvent;
|
||||
let rootWindowID;
|
||||
|
||||
function gotEvent(event, details)
|
||||
{
|
||||
if (!details.url.startsWith(BASE)) {
|
||||
return;
|
||||
}
|
||||
info(`Got ${event} ${details.url} ${details.windowId} ${details.parentWindowId}`);
|
||||
|
||||
is(details.browser, expectedBrowser, "correct <browser> element");
|
||||
|
||||
received.push({url: details.url, event});
|
||||
|
||||
if (typeof(rootWindowID) == "undefined") {
|
||||
rootWindowID = details.windowId;
|
||||
}
|
||||
|
||||
if (details.url == URL) {
|
||||
is(details.windowId, rootWindowID, "root window ID correct");
|
||||
} else {
|
||||
is(details.parentWindowId, rootWindowID, "parent window ID correct");
|
||||
isnot(details.windowId, rootWindowID, "window ID probably okay");
|
||||
}
|
||||
|
||||
isnot(details.windowId, undefined);
|
||||
isnot(details.parentWindowId, undefined);
|
||||
|
||||
if (details.url == waitingURL && event == waitingEvent) {
|
||||
completedResolve();
|
||||
}
|
||||
}
|
||||
|
||||
function loadViaFrameScript(url, event, script)
|
||||
{
|
||||
// Loading via a frame script ensures that the chrome process never
|
||||
// "gets ahead" of frame scripts in non-e10s mode.
|
||||
received = [];
|
||||
waitingURL = url;
|
||||
waitingEvent = event;
|
||||
expectedBrowser.messageManager.loadFrameScript("data:," + script, false);
|
||||
return new Promise(resolve => { completedResolve = resolve; });
|
||||
}
|
||||
|
||||
add_task(function* webnav_ordering() {
|
||||
let listeners = {};
|
||||
for (let event of EVENTS) {
|
||||
listeners[event] = gotEvent.bind(null, event);
|
||||
WebNavigation[event].addListener(listeners[event]);
|
||||
}
|
||||
|
||||
gBrowser.selectedTab = gBrowser.addTab();
|
||||
let browser = gBrowser.selectedBrowser;
|
||||
expectedBrowser = browser;
|
||||
|
||||
yield BrowserTestUtils.browserLoaded(browser);
|
||||
|
||||
yield loadViaFrameScript(URL, "onCompleted", `content.location = "${URL}";`);
|
||||
|
||||
function checkRequired(url) {
|
||||
for (let event of REQUIRED) {
|
||||
let found = false;
|
||||
for (let r of received) {
|
||||
if (r.url == url && r.event == event) {
|
||||
found = true;
|
||||
}
|
||||
}
|
||||
ok(found, `Received event ${event} from ${url}`);
|
||||
}
|
||||
}
|
||||
|
||||
checkRequired(URL);
|
||||
checkRequired(FRAME);
|
||||
|
||||
function checkBefore(action1, action2) {
|
||||
function find(action) {
|
||||
for (let i = 0; i < received.length; i++) {
|
||||
if (received[i].url == action.url && received[i].event == action.event) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
let index1 = find(action1);
|
||||
let index2 = find(action2);
|
||||
ok(index1 != -1, `Action ${JSON.stringify(action1)} happened`);
|
||||
ok(index2 != -1, `Action ${JSON.stringify(action2)} happened`);
|
||||
ok(index1 < index2, `Action ${JSON.stringify(action1)} happened before ${JSON.stringify(action2)}`);
|
||||
}
|
||||
|
||||
checkBefore({url: URL, event: "onCommitted"}, {url: FRAME, event: "onBeforeNavigate"});
|
||||
checkBefore({url: FRAME, event: "onCompleted"}, {url: URL, event: "onCompleted"});
|
||||
|
||||
yield loadViaFrameScript(FRAME2, "onCompleted", `content.frames[0].location = "${FRAME2}";`);
|
||||
|
||||
checkRequired(FRAME2);
|
||||
|
||||
yield loadViaFrameScript(FRAME2 + "#ref", "onReferenceFragmentUpdated",
|
||||
"content.frames[0].document.getElementById('elt').click();");
|
||||
|
||||
info("Received onReferenceFragmentUpdated from FRAME2");
|
||||
|
||||
gBrowser.removeCurrentTab();
|
||||
|
||||
for (let event of EVENTS) {
|
||||
WebNavigation[event].removeListener(listeners[event]);
|
||||
}
|
||||
});
|
||||
|
@ -0,0 +1,9 @@
|
||||
<!DOCTYPE HTML>
|
||||
|
||||
<html>
|
||||
<body>
|
||||
|
||||
<iframe src="file_WebNavigation_page2.html" width="200" height="200"></iframe>
|
||||
|
||||
</body>
|
||||
</html>
|
@ -0,0 +1,7 @@
|
||||
<!DOCTYPE HTML>
|
||||
|
||||
<html>
|
||||
<body>
|
||||
|
||||
</body>
|
||||
</html>
|
@ -0,0 +1,9 @@
|
||||
<!DOCTYPE HTML>
|
||||
|
||||
<html>
|
||||
<body>
|
||||
|
||||
<a id="elt" href="file_WebNavigation_page3.html#ref">click me</a>
|
||||
|
||||
</body>
|
||||
</html>
|
Loading…
Reference in New Issue
Block a user