2012-05-08 09:20:35 -07:00
|
|
|
/* 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";
|
|
|
|
|
|
|
|
let Cu = Components.utils;
|
|
|
|
let Ci = Components.interfaces;
|
|
|
|
let Cc = Components.classes;
|
2012-06-07 07:43:23 -07:00
|
|
|
let Cr = Components.results;
|
2012-06-07 10:26:34 -07:00
|
|
|
|
2012-05-11 00:47:40 -07:00
|
|
|
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
|
2012-06-07 07:43:23 -07:00
|
|
|
Cu.import("resource://gre/modules/Services.jsm");
|
|
|
|
Cu.import("resource://gre/modules/BrowserElementPromptService.jsm");
|
2012-05-08 09:20:35 -07:00
|
|
|
|
2012-05-23 07:15:31 -07:00
|
|
|
// Event whitelisted for bubbling.
|
|
|
|
let whitelistedEvents = [
|
|
|
|
Ci.nsIDOMKeyEvent.DOM_VK_ESCAPE, // Back button.
|
2012-06-11 12:58:46 -07:00
|
|
|
Ci.nsIDOMKeyEvent.DOM_VK_SLEEP, // Power button.
|
2012-05-23 07:15:31 -07:00
|
|
|
Ci.nsIDOMKeyEvent.DOM_VK_CONTEXT_MENU,
|
|
|
|
Ci.nsIDOMKeyEvent.DOM_VK_F5, // Search button.
|
|
|
|
Ci.nsIDOMKeyEvent.DOM_VK_PAGE_UP, // Volume up.
|
|
|
|
Ci.nsIDOMKeyEvent.DOM_VK_PAGE_DOWN // Volume down.
|
|
|
|
];
|
|
|
|
|
2012-05-08 09:20:35 -07:00
|
|
|
function debug(msg) {
|
2012-05-08 16:17:10 -07:00
|
|
|
//dump("BrowserElementChild - " + msg + "\n");
|
2012-05-08 09:20:35 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
function sendAsyncMsg(msg, data) {
|
|
|
|
sendAsyncMessage('browser-element-api:' + msg, data);
|
|
|
|
}
|
|
|
|
|
2012-05-11 13:03:51 -07:00
|
|
|
function sendSyncMsg(msg, data) {
|
|
|
|
return sendSyncMessage('browser-element-api:' + msg, data);
|
|
|
|
}
|
|
|
|
|
2012-05-08 09:20:35 -07:00
|
|
|
/**
|
|
|
|
* The BrowserElementChild implements one half of <iframe mozbrowser>.
|
|
|
|
* (The other half is, unsurprisingly, BrowserElementParent.)
|
|
|
|
*
|
|
|
|
* This script is injected into an <iframe mozbrowser> via
|
|
|
|
* nsIMessageManager::LoadFrameScript().
|
|
|
|
*
|
|
|
|
* Our job here is to listen for events within this frame and bubble them up to
|
|
|
|
* the parent process.
|
|
|
|
*/
|
|
|
|
|
2012-05-23 07:15:31 -07:00
|
|
|
var global = this;
|
|
|
|
|
2012-05-08 09:20:35 -07:00
|
|
|
function BrowserElementChild() {
|
2012-06-07 07:43:23 -07:00
|
|
|
// Maps outer window id --> weak ref to window. Used by modal dialog code.
|
|
|
|
this._windowIDDict = {};
|
2012-06-07 10:26:34 -07:00
|
|
|
|
|
|
|
this._init();
|
2012-05-08 09:20:35 -07:00
|
|
|
};
|
|
|
|
|
|
|
|
BrowserElementChild.prototype = {
|
|
|
|
_init: function() {
|
|
|
|
debug("Starting up.");
|
|
|
|
sendAsyncMsg("hello");
|
|
|
|
|
2012-06-07 07:43:23 -07:00
|
|
|
BrowserElementPromptService.mapWindowToBrowserElementChild(content, this);
|
|
|
|
|
2012-06-12 15:01:24 -07:00
|
|
|
docShell.isBrowserFrame = true;
|
2012-05-08 09:20:35 -07:00
|
|
|
docShell.QueryInterface(Ci.nsIWebProgress)
|
|
|
|
.addProgressListener(this._progressListener,
|
|
|
|
Ci.nsIWebProgress.NOTIFY_LOCATION |
|
2012-06-20 09:15:39 -07:00
|
|
|
Ci.nsIWebProgress.NOTIFY_SECURITY |
|
2012-05-08 09:20:35 -07:00
|
|
|
Ci.nsIWebProgress.NOTIFY_STATE_WINDOW);
|
|
|
|
|
2012-06-20 09:15:39 -07:00
|
|
|
// This is necessary to get security web progress notifications.
|
|
|
|
var securityUI = Cc['@mozilla.org/secure_browser_ui;1']
|
|
|
|
.createInstance(Ci.nsISecureBrowserUI);
|
|
|
|
securityUI.init(content);
|
|
|
|
|
2012-05-10 17:56:21 -07:00
|
|
|
// A mozbrowser iframe contained inside a mozapp iframe should return false
|
|
|
|
// for nsWindowUtils::IsPartOfApp (unless the mozbrowser iframe is itself
|
|
|
|
// also mozapp). That is, mozapp is transitive down to its children, but
|
|
|
|
// mozbrowser serves as a barrier.
|
|
|
|
//
|
|
|
|
// This is because mozapp iframes have some privileges which we don't want
|
|
|
|
// to extend to untrusted mozbrowser content.
|
2012-05-11 13:03:51 -07:00
|
|
|
//
|
2012-05-16 03:39:27 -07:00
|
|
|
// Get the app manifest from the parent, if our frame has one.
|
|
|
|
let appManifestURL = sendSyncMsg('get-mozapp-manifest-url')[0];
|
2012-05-16 03:40:09 -07:00
|
|
|
let windowUtils = content.QueryInterface(Ci.nsIInterfaceRequestor)
|
2012-05-23 07:15:31 -07:00
|
|
|
.getInterface(Ci.nsIDOMWindowUtils);
|
2012-05-16 03:40:09 -07:00
|
|
|
|
|
|
|
if (!!appManifestURL) {
|
|
|
|
windowUtils.setIsApp(true);
|
2012-05-22 06:06:29 -07:00
|
|
|
windowUtils.setApp(appManifestURL);
|
2012-05-16 03:40:09 -07:00
|
|
|
} else {
|
|
|
|
windowUtils.setIsApp(false);
|
|
|
|
}
|
2012-05-10 17:56:21 -07:00
|
|
|
|
2012-05-08 09:20:35 -07:00
|
|
|
addEventListener('DOMTitleChanged',
|
|
|
|
this._titleChangedHandler.bind(this),
|
|
|
|
/* useCapture = */ true,
|
|
|
|
/* wantsUntrusted = */ false);
|
2012-05-09 09:34:16 -07:00
|
|
|
|
|
|
|
addEventListener('DOMLinkAdded',
|
|
|
|
this._iconChangedHandler.bind(this),
|
|
|
|
/* useCapture = */ true,
|
|
|
|
/* wantsUntrusted = */ false);
|
2012-05-18 17:47:20 -07:00
|
|
|
|
2012-06-07 07:43:23 -07:00
|
|
|
var self = this;
|
|
|
|
function addMsgListener(msg, handler) {
|
|
|
|
addMessageListener('browser-element-api:' + msg, handler.bind(self));
|
|
|
|
}
|
2012-05-23 07:15:31 -07:00
|
|
|
|
2012-06-07 07:43:23 -07:00
|
|
|
addMsgListener("get-screenshot", this._recvGetScreenshot);
|
|
|
|
addMsgListener("set-visible", this._recvSetVisible);
|
2012-06-21 11:23:48 -07:00
|
|
|
addMsgListener("get-can-go-back", this._recvCanGoBack);
|
|
|
|
addMsgListener("get-can-go-forward", this._recvCanGoForward);
|
2012-06-07 07:43:23 -07:00
|
|
|
addMsgListener("unblock-modal-prompt", this._recvStopWaiting);
|
2012-05-27 05:39:04 -07:00
|
|
|
|
2012-05-23 07:15:31 -07:00
|
|
|
let els = Cc["@mozilla.org/eventlistenerservice;1"]
|
|
|
|
.getService(Ci.nsIEventListenerService);
|
|
|
|
|
|
|
|
// We are using the system group for those events so if something in the
|
|
|
|
// content called .stopPropagation() this will still be called.
|
|
|
|
els.addSystemEventListener(global, 'keydown',
|
|
|
|
this._keyEventHandler.bind(this),
|
|
|
|
/* useCapture = */ true);
|
|
|
|
els.addSystemEventListener(global, 'keypress',
|
|
|
|
this._keyEventHandler.bind(this),
|
|
|
|
/* useCapture = */ true);
|
|
|
|
els.addSystemEventListener(global, 'keyup',
|
|
|
|
this._keyEventHandler.bind(this),
|
|
|
|
/* useCapture = */ true);
|
2012-06-07 10:26:34 -07:00
|
|
|
els.addSystemEventListener(global, 'DOMWindowClose',
|
|
|
|
this._closeHandler.bind(this),
|
|
|
|
/* useCapture = */ false);
|
2012-05-08 09:20:35 -07:00
|
|
|
},
|
|
|
|
|
2012-06-07 07:43:23 -07:00
|
|
|
_tryGetInnerWindowID: function(win) {
|
|
|
|
let utils = win.QueryInterface(Ci.nsIInterfaceRequestor)
|
|
|
|
.getInterface(Ci.nsIDOMWindowUtils);
|
|
|
|
try {
|
|
|
|
return utils.currentInnerWindowID;
|
|
|
|
}
|
|
|
|
catch(e) {
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
},
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Show a modal prompt. Called by BrowserElementPromptService.
|
|
|
|
*/
|
|
|
|
showModalPrompt: function(win, args) {
|
|
|
|
let utils = win.QueryInterface(Ci.nsIInterfaceRequestor)
|
|
|
|
.getInterface(Ci.nsIDOMWindowUtils);
|
|
|
|
|
|
|
|
args.windowID = { outer: utils.outerWindowID,
|
|
|
|
inner: this._tryGetInnerWindowID(win) };
|
|
|
|
sendAsyncMsg('showmodalprompt', args);
|
|
|
|
|
|
|
|
let returnValue = this._waitForResult(win);
|
|
|
|
|
|
|
|
if (args.promptType == 'prompt' ||
|
|
|
|
args.promptType == 'confirm') {
|
|
|
|
return returnValue;
|
|
|
|
}
|
|
|
|
},
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Spin in a nested event loop until we receive a unblock-modal-prompt message for
|
|
|
|
* this window.
|
|
|
|
*/
|
|
|
|
_waitForResult: function(win) {
|
|
|
|
debug("_waitForResult(" + win + ")");
|
|
|
|
let utils = win.QueryInterface(Ci.nsIInterfaceRequestor)
|
|
|
|
.getInterface(Ci.nsIDOMWindowUtils);
|
|
|
|
|
|
|
|
let outerWindowID = utils.outerWindowID;
|
|
|
|
let innerWindowID = this._tryGetInnerWindowID(win);
|
|
|
|
if (innerWindowID === null) {
|
|
|
|
// I have no idea what waiting for a result means when there's no inner
|
|
|
|
// window, so let's just bail.
|
|
|
|
debug("_waitForResult: No inner window. Bailing.");
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
this._windowIDDict[outerWindowID] = Cu.getWeakReference(win);
|
|
|
|
|
|
|
|
debug("Entering modal state (outerWindowID=" + outerWindowID + ", " +
|
|
|
|
"innerWindowID=" + innerWindowID + ")");
|
|
|
|
|
|
|
|
// In theory, we're supposed to pass |modalStateWin| back to
|
|
|
|
// leaveModalStateWithWindow. But in practice, the window is always null,
|
|
|
|
// because it's the window associated with this script context, which
|
|
|
|
// doesn't have a window. But we'll play along anyway in case this
|
|
|
|
// changes.
|
|
|
|
var modalStateWin = utils.enterModalStateWithWindow();
|
|
|
|
|
|
|
|
// We'll decrement win.modalDepth when we receive a unblock-modal-prompt message
|
|
|
|
// for the window.
|
|
|
|
if (!win.modalDepth) {
|
|
|
|
win.modalDepth = 0;
|
|
|
|
}
|
|
|
|
win.modalDepth++;
|
|
|
|
let origModalDepth = win.modalDepth;
|
|
|
|
|
|
|
|
let thread = Services.tm.currentThread;
|
|
|
|
debug("Nested event loop - begin");
|
|
|
|
while (win.modalDepth == origModalDepth) {
|
|
|
|
// Bail out of the loop if the inner window changed; that means the
|
|
|
|
// window navigated.
|
|
|
|
if (this._tryGetInnerWindowID(win) !== innerWindowID) {
|
|
|
|
debug("_waitForResult: Inner window ID changed " +
|
|
|
|
"while in nested event loop.");
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
thread.processNextEvent(/* mayWait = */ true);
|
|
|
|
}
|
|
|
|
debug("Nested event loop - finish");
|
|
|
|
|
|
|
|
// If we exited the loop because the inner window changed, then bail on the
|
|
|
|
// modal prompt.
|
|
|
|
if (innerWindowID !== this._tryGetInnerWindowID(win)) {
|
|
|
|
throw Components.Exception("Modal state aborted by navigation",
|
|
|
|
Cr.NS_ERROR_NOT_AVAILABLE);
|
|
|
|
}
|
|
|
|
|
|
|
|
let returnValue = win.modalReturnValue;
|
|
|
|
delete win.modalReturnValue;
|
|
|
|
|
|
|
|
utils.leaveModalStateWithWindow(modalStateWin);
|
|
|
|
|
|
|
|
debug("Leaving modal state (outerID=" + outerWindowID + ", " +
|
|
|
|
"innerID=" + innerWindowID + ")");
|
|
|
|
return returnValue;
|
|
|
|
},
|
|
|
|
|
|
|
|
_recvStopWaiting: function(msg) {
|
|
|
|
let outerID = msg.json.windowID.outer;
|
|
|
|
let innerID = msg.json.windowID.inner;
|
|
|
|
let returnValue = msg.json.returnValue;
|
|
|
|
debug("recvStopWaiting(outer=" + outerID + ", inner=" + innerID +
|
|
|
|
", returnValue=" + returnValue + ")");
|
|
|
|
|
|
|
|
if (!this._windowIDDict[outerID]) {
|
|
|
|
debug("recvStopWaiting: No record of outer window ID " + outerID);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
let win = this._windowIDDict[outerID].get();
|
|
|
|
delete this._windowIDDict[outerID];
|
|
|
|
|
|
|
|
if (!win) {
|
|
|
|
debug("recvStopWaiting, but window is gone\n");
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (innerID !== this._tryGetInnerWindowID(win)) {
|
|
|
|
debug("recvStopWaiting, but inner ID has changed\n");
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
debug("recvStopWaiting " + win);
|
|
|
|
win.modalReturnValue = returnValue;
|
|
|
|
win.modalDepth--;
|
|
|
|
},
|
|
|
|
|
2012-05-08 09:20:35 -07:00
|
|
|
_titleChangedHandler: function(e) {
|
|
|
|
debug("Got titlechanged: (" + e.target.title + ")");
|
|
|
|
var win = e.target.defaultView;
|
|
|
|
|
|
|
|
// Ignore titlechanges which don't come from the top-level
|
|
|
|
// <iframe mozbrowser> window.
|
|
|
|
if (win == content) {
|
|
|
|
sendAsyncMsg('titlechange', e.target.title);
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
debug("Not top level!");
|
|
|
|
}
|
|
|
|
},
|
|
|
|
|
2012-05-09 09:34:16 -07:00
|
|
|
_iconChangedHandler: function(e) {
|
|
|
|
debug("Got iconchanged: (" + e.target.href + ")");
|
|
|
|
var hasIcon = e.target.rel.split(' ').some(function(x) {
|
|
|
|
return x.toLowerCase() === 'icon';
|
|
|
|
});
|
|
|
|
|
|
|
|
if (hasIcon) {
|
|
|
|
var win = e.target.ownerDocument.defaultView;
|
|
|
|
// Ignore iconchanges which don't come from the top-level
|
|
|
|
// <iframe mozbrowser> window.
|
|
|
|
if (win == content) {
|
|
|
|
sendAsyncMsg('iconchange', e.target.href);
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
debug("Not top level!");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
},
|
|
|
|
|
2012-06-07 10:26:34 -07:00
|
|
|
_closeHandler: function(e) {
|
|
|
|
let win = e.target;
|
|
|
|
if (win != content || e.defaultPrevented) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
debug("Closing window " + win);
|
|
|
|
sendAsyncMsg('close');
|
|
|
|
|
|
|
|
// Inform the window implementation that we handled this close ourselves.
|
|
|
|
e.preventDefault();
|
|
|
|
},
|
|
|
|
|
2012-05-18 17:47:20 -07:00
|
|
|
_recvGetScreenshot: function(data) {
|
|
|
|
debug("Received getScreenshot message: (" + data.json.id + ")");
|
|
|
|
var canvas = content.document
|
|
|
|
.createElementNS("http://www.w3.org/1999/xhtml", "canvas");
|
|
|
|
var ctx = canvas.getContext("2d");
|
|
|
|
canvas.mozOpaque = true;
|
|
|
|
canvas.height = content.innerHeight;
|
|
|
|
canvas.width = content.innerWidth;
|
|
|
|
ctx.drawWindow(content, 0, 0, content.innerWidth,
|
|
|
|
content.innerHeight, "rgb(255,255,255)");
|
|
|
|
sendAsyncMsg('got-screenshot', {
|
|
|
|
id: data.json.id,
|
2012-06-21 11:23:48 -07:00
|
|
|
rv: canvas.toDataURL("image/png")
|
2012-05-18 17:47:20 -07:00
|
|
|
});
|
|
|
|
},
|
|
|
|
|
2012-05-27 05:39:04 -07:00
|
|
|
_recvSetVisible: function(data) {
|
|
|
|
debug("Received setVisible message: (" + data.json.visible + ")");
|
|
|
|
if (docShell.isActive !== data.json.visible) {
|
|
|
|
docShell.isActive = data.json.visible;
|
|
|
|
}
|
|
|
|
},
|
|
|
|
|
2012-06-21 11:23:48 -07:00
|
|
|
_recvCanGoBack: function(data) {
|
|
|
|
var webNav = docShell.QueryInterface(Ci.nsIWebNavigation);
|
|
|
|
sendAsyncMsg('got-can-go-back', {
|
|
|
|
id: data.json.id,
|
|
|
|
rv: webNav.canGoBack
|
|
|
|
});
|
|
|
|
},
|
|
|
|
|
|
|
|
_recvCanGoForward: function(data) {
|
|
|
|
var webNav = docShell.QueryInterface(Ci.nsIWebNavigation);
|
|
|
|
sendAsyncMsg('got-can-go-forward', {
|
|
|
|
id: data.json.id,
|
|
|
|
rv: webNav.canGoForward
|
|
|
|
});
|
|
|
|
},
|
|
|
|
|
2012-05-23 07:15:31 -07:00
|
|
|
_keyEventHandler: function(e) {
|
|
|
|
if (whitelistedEvents.indexOf(e.keyCode) != -1 && !e.defaultPrevented) {
|
|
|
|
sendAsyncMsg('keyevent', {
|
|
|
|
type: e.type,
|
2012-05-31 07:40:33 -07:00
|
|
|
keyCode: e.keyCode,
|
2012-05-23 07:15:31 -07:00
|
|
|
charCode: e.charCode,
|
|
|
|
});
|
|
|
|
}
|
|
|
|
},
|
|
|
|
|
2012-05-08 09:20:35 -07:00
|
|
|
// The docShell keeps a weak reference to the progress listener, so we need
|
|
|
|
// to keep a strong ref to it ourselves.
|
|
|
|
_progressListener: {
|
|
|
|
QueryInterface: XPCOMUtils.generateQI([Ci.nsIWebProgressListener,
|
2012-06-07 07:43:23 -07:00
|
|
|
Ci.nsISupportsWeakReference]),
|
2012-05-08 09:20:35 -07:00
|
|
|
_seenLoadStart: false,
|
|
|
|
|
|
|
|
onLocationChange: function(webProgress, request, location, flags) {
|
|
|
|
// We get progress events from subshells here, which is kind of weird.
|
|
|
|
if (webProgress != docShell) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Ignore locationchange events which occur before the first loadstart.
|
|
|
|
// These are usually about:blank loads we don't care about.
|
|
|
|
if (!this._seenLoadStart) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
sendAsyncMsg('locationchange', location.spec);
|
|
|
|
},
|
|
|
|
|
|
|
|
onStateChange: function(webProgress, request, stateFlags, status) {
|
|
|
|
if (webProgress != docShell) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (stateFlags & Ci.nsIWebProgressListener.STATE_START) {
|
|
|
|
this._seenLoadStart = true;
|
|
|
|
sendAsyncMsg('loadstart');
|
|
|
|
}
|
|
|
|
|
|
|
|
if (stateFlags & Ci.nsIWebProgressListener.STATE_STOP) {
|
|
|
|
sendAsyncMsg('loadend');
|
|
|
|
}
|
|
|
|
},
|
|
|
|
|
2012-06-20 09:15:39 -07:00
|
|
|
onSecurityChange: function(webProgress, request, state) {
|
|
|
|
if (webProgress != docShell) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
var stateDesc;
|
|
|
|
if (state & Ci.nsIWebProgressListener.STATE_IS_SECURE) {
|
|
|
|
stateDesc = 'secure';
|
|
|
|
}
|
|
|
|
else if (state & Ci.nsIWebProgressListener.STATE_IS_BROKEN) {
|
|
|
|
stateDesc = 'broken';
|
|
|
|
}
|
|
|
|
else if (state & Ci.nsIWebProgressListener.STATE_IS_INSECURE) {
|
|
|
|
stateDesc = 'insecure';
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
debug("Unexpected securitychange state!");
|
|
|
|
stateDesc = '???';
|
|
|
|
}
|
|
|
|
|
|
|
|
// XXX Until bug 764496 is fixed, this will always return false.
|
|
|
|
var isEV = !!(state & Ci.nsIWebProgressListener.STATE_IDENTITY_EV_TOPLEVEL);
|
|
|
|
|
|
|
|
sendAsyncMsg('securitychange', {state: stateDesc, extendedValidation: isEV});
|
|
|
|
},
|
|
|
|
|
2012-05-08 09:20:35 -07:00
|
|
|
onStatusChange: function(webProgress, request, status, message) {},
|
|
|
|
onProgressChange: function(webProgress, request, curSelfProgress,
|
|
|
|
maxSelfProgress, curTotalProgress, maxTotalProgress) {},
|
|
|
|
},
|
|
|
|
};
|
|
|
|
|
|
|
|
var api = new BrowserElementChild();
|