Bug 1256652 - [webext] Initial support of webNavigation transition types and qualifiers. r=krizsa

- transition types: reload, link, auto_subframe
- transition qualifiers: forward_back, server_redirect

MozReview-Commit-ID: Bx3oG2fuWuv

--HG--
extra : transplant_source : 0%B6%F4DE%80%B6%CA%9B%09%81%1C%16%96%F6%C0%1FC%B0%F9
This commit is contained in:
Luca Greco 2016-04-15 14:49:13 +02:00
parent d7b2387a81
commit 94d2a46537
4 changed files with 218 additions and 13 deletions

View File

@ -18,6 +18,60 @@ var {
runSafe,
} = ExtensionUtils;
const defaultTransitionTypes = {
topFrame: "link",
subFrame: "auto_subframe",
};
const frameTransitions = {
anyFrame: {
qualifiers: ["server_redirect", "client_redirect", "forward_back"],
},
topFrame: {
types: ["reload", "form_submit"],
},
};
function isTopLevelFrame({frameId, parentFrameId}) {
return frameId == 0 && parentFrameId == -1;
}
function fillTransitionProperties(eventName, src, dst) {
if (eventName == "onCommitted" || eventName == "onHistoryStateUpdated") {
let frameTransitionData = src.frameTransitionData || {};
let transitionType, transitionQualifiers = [];
// Fill transition properties for any frame.
for (let qualifier of frameTransitions.anyFrame.qualifiers) {
if (frameTransitionData[qualifier]) {
transitionQualifiers.push(qualifier);
}
}
if (isTopLevelFrame(dst)) {
for (let type of frameTransitions.topFrame.types) {
if (frameTransitionData[type]) {
transitionType = type;
}
}
// If transitionType is not defined, defaults it to "link".
if (!transitionType) {
transitionType = defaultTransitionTypes.topFrame;
}
} else {
// If it is sub-frame, transitionType defaults it to "auto_subframe",
// "manual_subframe" is set only in case of a recent user interaction.
transitionType = defaultTransitionTypes.subFrame;
}
// Fill the transition properties in the webNavigation event object.
dst.transitionType = transitionType;
dst.transitionQualifiers = transitionQualifiers;
}
}
// Similar to WebRequestEventManager but for WebNavigation.
function WebNavigationEventManager(context, eventName) {
let name = `webNavigation.${eventName}`;
@ -46,6 +100,8 @@ function WebNavigationEventManager(context, eventName) {
return;
}
fillTransitionProperties(eventName, data, data2);
runSafe(context, callback, data2);
};

View File

@ -70,6 +70,8 @@ const URL = BASE + "/file_WebNavigation_page1.html";
const FRAME = BASE + "/file_WebNavigation_page2.html";
const FRAME2 = BASE + "/file_WebNavigation_page3.html";
const FRAME_PUSHSTATE = BASE + "/file_WebNavigation_page3_pushState.html";
const REDIRECT = BASE + "/redirection.sjs";
const REDIRECTED = BASE + "/dummy_page.html";
const REQUIRED = [
"onBeforeNavigate",
@ -91,6 +93,120 @@ function loadAndWait(win, event, url, script) {
return new Promise(resolve => { completedResolve = resolve; });
}
add_task(function* webnav_transitions_props() {
function backgroundScriptTransitions() {
const EVENTS = [
"onCommitted",
"onCompleted",
];
function gotEvent(event, details) {
browser.test.log(`Got ${event} ${details.url} ${details.transitionType}`);
browser.test.sendMessage("received", {url: details.url, details, event});
}
let listeners = {};
for (let event of EVENTS) {
listeners[event] = gotEvent.bind(null, event);
browser.webNavigation[event].addListener(listeners[event]);
}
browser.test.sendMessage("ready");
}
let extensionData = {
manifest: {
permissions: [
"webNavigation",
],
},
background: `(${backgroundScriptTransitions})()`,
};
let extension = ExtensionTestUtils.loadExtension(extensionData);
extension.onMessage("received", ({url, event, details}) => {
received.push({url, event, details});
if (event == waitingEvent && url == waitingURL) {
completedResolve();
}
});
yield Promise.all([extension.startup(), extension.awaitMessage("ready")]);
info("webnavigation extension loaded");
let win = window.open();
yield loadAndWait(win, "onCompleted", URL, () => { win.location = URL; });
// transitionType: reload
received = [];
yield loadAndWait(win, "onCompleted", URL, () => { win.location.reload(); });
let found = received.find((data) => (data.event == "onCommitted" && data.url == URL));
ok(found, "Got the onCommitted event");
if (found) {
is(found.details.transitionType, "reload",
"Got the expected 'reload' transitionType in the OnCommitted event");
ok(Array.isArray(found.details.transitionQualifiers),
"transitionQualifiers found in the OnCommitted events");
}
// transitionType: auto_subframe
found = received.find((data) => (data.event == "onCommitted" && data.url == FRAME));
ok(found, "Got the sub-frame onCommitted event");
if (found) {
is(found.details.transitionType, "auto_subframe",
"Got the expected 'auto_subframe' transitionType in the OnCommitted event");
ok(Array.isArray(found.details.transitionQualifiers),
"transitionQualifiers found in the OnCommitted events");
}
// transitionQualifier: server_redirect
received = [];
yield loadAndWait(win, "onCompleted", REDIRECTED, () => { win.location = REDIRECT; });
found = received.find((data) => (data.event == "onCommitted" && data.url == REDIRECTED));
ok(found, "Got the onCommitted event");
if (found) {
is(found.details.transitionType, "link",
"Got the expected 'link' transitionType in the OnCommitted event");
ok(Array.isArray(found.details.transitionQualifiers) &&
found.details.transitionQualifiers.find((q) => q == "server_redirect"),
"Got the expected 'server_redirect' transitionQualifiers in the OnCommitted events");
}
// transitionQualifier: forward_back
received = [];
yield loadAndWait(win, "onCompleted", URL, () => { win.history.back(); });
found = received.find((data) => (data.event == "onCommitted" && data.url == URL));
ok(found, "Got the onCommitted event");
if (found) {
is(found.details.transitionType, "link",
"Got the expected 'link' transitionType in the OnCommitted event");
ok(Array.isArray(found.details.transitionQualifiers) &&
found.details.transitionQualifiers.find((q) => q == "forward_back"),
"Got the expected 'forward_back' transitionQualifiers in the OnCommitted events");
}
// cleanup phase
win.close();
yield extension.unload();
info("webnavigation extension unloaded");
});
add_task(function* webnav_ordering() {
let extensionData = {
manifest: {

View File

@ -14,10 +14,7 @@ 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
// onCreatedNavigationTarget
var Manager = {
listeners: new Map(),
@ -104,18 +101,26 @@ var Manager = {
},
onDocumentChange(browser, data) {
let url = data.location;
let extra = {
url: data.location,
// Transition data which is coming from the content process.
frameTransitionData: data.frameTransitionData,
};
this.fire("onCommitted", browser, data, {url});
this.fire("onCommitted", browser, data, extra);
},
onHistoryChange(browser, data) {
let url = data.location;
let extra = {
url: data.location,
// Transition data which is coming from the content process.
frameTransitionData: data.frameTransitionData,
};
if (data.isReferenceFragmentUpdated) {
this.fire("onReferenceFragmentUpdated", browser, data, {url});
this.fire("onReferenceFragmentUpdated", browser, data, extra);
} else if (data.isHistoryStateUpdated) {
this.fire("onHistoryStateUpdated", browser, data, {url});
this.fire("onHistoryStateUpdated", browser, data, extra);
}
},

View File

@ -81,7 +81,7 @@ var WebProgressListener = {
// (see Bug 1264936 and Bug 125662 for rationale)
if ((webProgress.DOMWindow.top != webProgress.DOMWindow) &&
(stateFlags & Ci.nsIWebProgressListener.STATE_IS_DOCUMENT)) {
this.sendDocumentChange({webProgress, locationURI});
this.sendDocumentChange({webProgress, locationURI, request});
}
},
@ -103,7 +103,7 @@ var WebProgressListener = {
// an "Extension:HistoryChange" to the main process, where it will be turned
// into a webNavigation.onHistoryStateUpdated/onReferenceFragmentUpdated event.
if (isSameDocument) {
this.sendHistoryChange({webProgress, previousURI, locationURI});
this.sendHistoryChange({webProgress, previousURI, locationURI, request});
} else if (webProgress.DOMWindow.top == webProgress.DOMWindow) {
// We have to catch the document changes from top level frames here,
// where we can detect the "server redirect" transition.
@ -124,8 +124,12 @@ var WebProgressListener = {
sendAsyncMessage("Extension:StateChange", data);
},
sendDocumentChange({webProgress, locationURI}) {
sendDocumentChange({webProgress, locationURI, request}) {
let {loadType} = webProgress;
let frameTransitionData = this.getFrameTransitionData({loadType, request});
let data = {
frameTransitionData,
location: locationURI ? locationURI.spec : "",
windowId: webProgress.DOMWindowID,
parentWindowId: WebNavigationFrames.getParentWindowId(webProgress.DOMWindow),
@ -134,7 +138,7 @@ var WebProgressListener = {
sendAsyncMessage("Extension:DocumentChange", data);
},
sendHistoryChange({webProgress, previousURI, locationURI}) {
sendHistoryChange({webProgress, previousURI, locationURI, request}) {
let {loadType} = webProgress;
let isHistoryStateUpdated = false;
@ -159,7 +163,10 @@ var WebProgressListener = {
}
if (isHistoryStateUpdated || isReferenceFragmentUpdated) {
let frameTransitionData = this.getFrameTransitionData({loadType, request});
let data = {
frameTransitionData,
isHistoryStateUpdated, isReferenceFragmentUpdated,
location: locationURI ? locationURI.spec : "",
windowId: webProgress.DOMWindowID,
@ -170,6 +177,27 @@ var WebProgressListener = {
}
},
getFrameTransitionData({loadType, request}) {
let frameTransitionData = {};
if (loadType & Ci.nsIDocShell.LOAD_CMD_HISTORY) {
frameTransitionData.forward_back = true;
}
if (loadType & Ci.nsIDocShell.LOAD_CMD_RELOAD) {
frameTransitionData.reload = true;
}
if (request instanceof Ci.nsIChannel) {
if (request.loadInfo.redirectChain.length) {
frameTransitionData.server_redirect = true;
}
}
return frameTransitionData;
},
QueryInterface: XPCOMUtils.generateQI([Ci.nsIWebProgressListener, Ci.nsISupportsWeakReference]),
};