gecko-dev/dom/browser-element/BrowserElementProxy.js

221 lines
7.5 KiB
JavaScript

/* 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 Cc = Components.classes;
const Ci = Components.interfaces;
const Cu = Components.utils;
const Cr = Components.results;
Cu.import('resource://gre/modules/XPCOMUtils.jsm');
Cu.import('resource://gre/modules/Services.jsm');
function defineNoReturnMethod(methodName) {
return function noReturnMethod() {
let args = Array.slice(arguments);
this._sendToParent(methodName, args);
};
}
function defineDOMRequestMethod(methodName) {
return function domRequestMethod() {
let args = Array.slice(arguments);
return this._sendDOMRequest(methodName, args);
};
}
function defineUnimplementedMethod(methodName) {
return function unimplementedMethod() {
throw Components.Exception(
'Unimplemented method: ' + methodName, Cr.NS_ERROR_FAILURE);
};
}
/**
* The BrowserElementProxy talks to the Browser IFrameElement instance on
* behave of the embedded document. It implements all the methods on
* the Browser IFrameElement and the methods will work if they are applicable.
*
* The message is forwarded to BrowserElementParent.js by creating an
* 'browser-element-api:proxy-call' observer message.
* BrowserElementChildPreload will get notified and send the message through
* to the main process through sendAsyncMessage with message of the same name.
*
* The return message will follow the same route. The message name on the
* return route is 'browser-element-api:proxy'.
*
* Both BrowserElementProxy and BrowserElementParent must be modified if there
* is a new method implemented, or a new event added to the Browser
* IFrameElement.
*
* Other details unmentioned here are checks of message sender and recipients
* to identify proxy instance on different innerWindows or ensure the content
* process has the right permission.
*/
function BrowserElementProxy() {
// Pad the 0th element so that DOMRequest ID will always be a truthy value.
this._pendingDOMRequests = [ undefined ];
}
BrowserElementProxy.prototype = {
classDescription: 'BrowserElementProxy allowed embedded frame to control ' +
'it\'s own embedding browser element frame instance.',
classID: Components.ID('{7e95d54c-9930-49c8-9a10-44fe40fe8251}'),
contractID: '@mozilla.org/dom/browser-element-proxy;1',
QueryInterface: XPCOMUtils.generateQI([
Ci.nsIDOMGlobalPropertyInitializer,
Ci.nsIObserver]),
_window: null,
_innerWindowID: undefined,
get allowedAudioChannels() {
return this._window.navigator.mozAudioChannelManager ?
this._window.navigator.mozAudioChannelManager.allowedAudioChannels :
null;
},
init: function(win) {
this._window = win;
this._innerWindowID = win.QueryInterface(Ci.nsIInterfaceRequestor)
.getInterface(Ci.nsIDOMWindowUtils)
.currentInnerWindowID;
this._sendToParent('_proxyInstanceInit');
Services.obs.addObserver(this, 'browser-element-api:proxy', false);
},
uninit: function(win) {
this._sendToParent('_proxyInstanceUninit');
this._window = null;
this._innerWindowID = undefined;
Services.obs.removeObserver(this, 'browser-element-api:proxy');
},
observe: function(subject, topic, stringifedData) {
let data = JSON.parse(stringifedData);
if (subject !== this._window ||
data.innerWindowID !== data.innerWindowID) {
return;
}
if (data.eventName) {
this._fireEvent(data.eventName, JSON.parse(data.eventDetailString));
return;
}
if ('domRequestId' in data) {
let req = this._pendingDOMRequests[data.domRequestId];
this._pendingDOMRequests[data.domRequestId] = undefined;
if (!req) {
dump('BrowserElementProxy Error: ' +
'Multiple observer messages for the same DOMRequest result.\n');
return;
}
if ('result' in data) {
let clientObj = Cu.cloneInto(data.result, this._window);
Services.DOMRequest.fireSuccess(req, clientObj);
} else {
let clientObj = Cu.cloneInto(data.error, this._window);
Services.DOMRequest.fireSuccess(req, clientObj);
}
return;
}
dump('BrowserElementProxy Error: ' +
'Received unhandled observer messages ' + stringifedData + '.\n');
},
_sendDOMRequest: function(methodName, args) {
let id = this._pendingDOMRequests.length;
let req = Services.DOMRequest.createRequest(this._window);
this._pendingDOMRequests.push(req);
this._sendToParent(methodName, args, id);
return req;
},
_sendToParent: function(methodName, args, domRequestId) {
let data = {
methodName: methodName,
args: args,
innerWindowID: this._innerWindowID
};
if (domRequestId) {
data.domRequestId = domRequestId;
}
Services.obs.notifyObservers(
this._window, 'browser-element-api:proxy-call', JSON.stringify(data));
},
_fireEvent: function(name, detail) {
let evt = this._createEvent(name, detail,
/* cancelable = */ false);
this.__DOM_IMPL__.dispatchEvent(evt);
},
_createEvent: function(evtName, detail, cancelable) {
// This will have to change if we ever want to send a CustomEvent with null
// detail. For now, it's OK.
if (detail !== undefined && detail !== null) {
detail = Cu.cloneInto(detail, this._window);
return new this._window.CustomEvent(evtName,
{ bubbles: false,
cancelable: cancelable,
detail: detail });
}
return new this._window.Event(evtName,
{ bubbles: false,
cancelable: cancelable });
},
setVisible: defineNoReturnMethod('setVisible'),
setActive: defineNoReturnMethod('setActive'),
sendMouseEvent: defineNoReturnMethod('sendMouseEvent'),
sendTouchEvent: defineNoReturnMethod('sendTouchEvent'),
goBack: defineNoReturnMethod('goBack'),
goForward: defineNoReturnMethod('goForward'),
reload: defineNoReturnMethod('reload'),
stop: defineNoReturnMethod('stop'),
zoom: defineNoReturnMethod('zoom'),
setNFCFocus: defineNoReturnMethod('setNFCFocus'),
findAll: defineNoReturnMethod('findAll'),
findNext: defineNoReturnMethod('findNext'),
clearMatch: defineNoReturnMethod('clearMatch'),
mute: defineNoReturnMethod('mute'),
unmute: defineNoReturnMethod('unmute'),
setVolume: defineNoReturnMethod('setVolume'),
getVisible: defineDOMRequestMethod('getVisible'),
download: defineDOMRequestMethod('download'),
purgeHistory: defineDOMRequestMethod('purgeHistory'),
getCanGoBack: defineDOMRequestMethod('getCanGoBack'),
getCanGoForward: defineDOMRequestMethod('getCanGoForward'),
getContentDimensions: defineDOMRequestMethod('getContentDimensions'),
setInputMethodActive: defineDOMRequestMethod('setInputMethodActive'),
executeScript: defineDOMRequestMethod('executeScript'),
getMuted: defineDOMRequestMethod('getMuted'),
getVolume: defineDOMRequestMethod('getVolume'),
getActive: defineUnimplementedMethod('getActive'),
addNextPaintListener: defineUnimplementedMethod('addNextPaintListener'),
removeNextPaintListener: defineUnimplementedMethod('removeNextPaintListener'),
getScreenshot: defineUnimplementedMethod('getScreenshot')
};
this.NSGetFactory = XPCOMUtils.generateNSGetFactory([BrowserElementProxy]);