1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 1.2 +++ b/dom/browser-element/BrowserElementParent.jsm Wed Dec 31 06:09:35 2014 +0100 1.3 @@ -0,0 +1,855 @@ 1.4 +/* This Source Code Form is subject to the terms of the Mozilla Public 1.5 + * License, v. 2.0. If a copy of the MPL was not distributed with this file, 1.6 + * You can obtain one at http://mozilla.org/MPL/2.0/. */ 1.7 + 1.8 +"use strict"; 1.9 + 1.10 +let Cu = Components.utils; 1.11 +let Ci = Components.interfaces; 1.12 +let Cc = Components.classes; 1.13 +let Cr = Components.results; 1.14 + 1.15 +/* BrowserElementParent injects script to listen for certain events in the 1.16 + * child. We then listen to messages from the child script and take 1.17 + * appropriate action here in the parent. 1.18 + */ 1.19 + 1.20 +this.EXPORTED_SYMBOLS = ["BrowserElementParentBuilder"]; 1.21 + 1.22 +Cu.import("resource://gre/modules/Services.jsm"); 1.23 +Cu.import("resource://gre/modules/XPCOMUtils.jsm"); 1.24 +Cu.import("resource://gre/modules/BrowserElementPromptService.jsm"); 1.25 + 1.26 +XPCOMUtils.defineLazyGetter(this, "DOMApplicationRegistry", function () { 1.27 + Cu.import("resource://gre/modules/Webapps.jsm"); 1.28 + return DOMApplicationRegistry; 1.29 +}); 1.30 + 1.31 +const TOUCH_EVENTS_ENABLED_PREF = "dom.w3c_touch_events.enabled"; 1.32 + 1.33 +function debug(msg) { 1.34 + //dump("BrowserElementParent.jsm - " + msg + "\n"); 1.35 +} 1.36 + 1.37 +function getIntPref(prefName, def) { 1.38 + try { 1.39 + return Services.prefs.getIntPref(prefName); 1.40 + } 1.41 + catch(err) { 1.42 + return def; 1.43 + } 1.44 +} 1.45 + 1.46 +function exposeAll(obj) { 1.47 + // Filter for Objects and Arrays. 1.48 + if (typeof obj !== "object" || !obj) 1.49 + return; 1.50 + 1.51 + // Recursively expose our children. 1.52 + Object.keys(obj).forEach(function(key) { 1.53 + exposeAll(obj[key]); 1.54 + }); 1.55 + 1.56 + // If we're not an Array, generate an __exposedProps__ object for ourselves. 1.57 + if (obj instanceof Array) 1.58 + return; 1.59 + var exposed = {}; 1.60 + Object.keys(obj).forEach(function(key) { 1.61 + exposed[key] = 'rw'; 1.62 + }); 1.63 + obj.__exposedProps__ = exposed; 1.64 +} 1.65 + 1.66 +function defineAndExpose(obj, name, value) { 1.67 + obj[name] = value; 1.68 + if (!('__exposedProps__' in obj)) 1.69 + obj.__exposedProps__ = {}; 1.70 + obj.__exposedProps__[name] = 'r'; 1.71 +} 1.72 + 1.73 +function visibilityChangeHandler(e) { 1.74 + // The visibilitychange event's target is the document. 1.75 + let win = e.target.defaultView; 1.76 + 1.77 + if (!win._browserElementParents) { 1.78 + return; 1.79 + } 1.80 + 1.81 + let beps = Cu.nondeterministicGetWeakMapKeys(win._browserElementParents); 1.82 + if (beps.length == 0) { 1.83 + win.removeEventListener('visibilitychange', visibilityChangeHandler); 1.84 + return; 1.85 + } 1.86 + 1.87 + for (let i = 0; i < beps.length; i++) { 1.88 + beps[i]._ownerVisibilityChange(); 1.89 + } 1.90 +} 1.91 + 1.92 +this.BrowserElementParentBuilder = { 1.93 + create: function create(frameLoader, hasRemoteFrame, isPendingFrame) { 1.94 + return new BrowserElementParent(frameLoader, hasRemoteFrame); 1.95 + } 1.96 +} 1.97 + 1.98 + 1.99 +// The active input method iframe. 1.100 +let activeInputFrame = null; 1.101 + 1.102 +function BrowserElementParent(frameLoader, hasRemoteFrame, isPendingFrame) { 1.103 + debug("Creating new BrowserElementParent object for " + frameLoader); 1.104 + this._domRequestCounter = 0; 1.105 + this._pendingDOMRequests = {}; 1.106 + this._hasRemoteFrame = hasRemoteFrame; 1.107 + this._nextPaintListeners = []; 1.108 + 1.109 + this._frameLoader = frameLoader; 1.110 + this._frameElement = frameLoader.QueryInterface(Ci.nsIFrameLoader).ownerElement; 1.111 + let self = this; 1.112 + if (!this._frameElement) { 1.113 + debug("No frame element?"); 1.114 + return; 1.115 + } 1.116 + 1.117 + Services.obs.addObserver(this, 'ask-children-to-exit-fullscreen', /* ownsWeak = */ true); 1.118 + Services.obs.addObserver(this, 'oop-frameloader-crashed', /* ownsWeak = */ true); 1.119 + 1.120 + let defineMethod = function(name, fn) { 1.121 + XPCNativeWrapper.unwrap(self._frameElement)[name] = function() { 1.122 + if (self._isAlive()) { 1.123 + return fn.apply(self, arguments); 1.124 + } 1.125 + }; 1.126 + } 1.127 + 1.128 + let defineNoReturnMethod = function(name, fn) { 1.129 + XPCNativeWrapper.unwrap(self._frameElement)[name] = function method() { 1.130 + if (!self._mm) { 1.131 + // Remote browser haven't been created, we just queue the API call. 1.132 + let args = Array.slice(arguments); 1.133 + args.unshift(self); 1.134 + self._pendingAPICalls.push(method.bind.apply(fn, args)); 1.135 + return; 1.136 + } 1.137 + if (self._isAlive()) { 1.138 + fn.apply(self, arguments); 1.139 + } 1.140 + }; 1.141 + }; 1.142 + 1.143 + let defineDOMRequestMethod = function(domName, msgName) { 1.144 + XPCNativeWrapper.unwrap(self._frameElement)[domName] = function() { 1.145 + if (!self._mm) { 1.146 + return self._queueDOMRequest; 1.147 + } 1.148 + if (self._isAlive()) { 1.149 + return self._sendDOMRequest(msgName); 1.150 + } 1.151 + }; 1.152 + } 1.153 + 1.154 + // Define methods on the frame element. 1.155 + defineNoReturnMethod('setVisible', this._setVisible); 1.156 + defineDOMRequestMethod('getVisible', 'get-visible'); 1.157 + defineNoReturnMethod('sendMouseEvent', this._sendMouseEvent); 1.158 + 1.159 + // 0 = disabled, 1 = enabled, 2 - auto detect 1.160 + if (getIntPref(TOUCH_EVENTS_ENABLED_PREF, 0) != 0) { 1.161 + defineNoReturnMethod('sendTouchEvent', this._sendTouchEvent); 1.162 + } 1.163 + defineNoReturnMethod('goBack', this._goBack); 1.164 + defineNoReturnMethod('goForward', this._goForward); 1.165 + defineNoReturnMethod('reload', this._reload); 1.166 + defineNoReturnMethod('stop', this._stop); 1.167 + defineDOMRequestMethod('purgeHistory', 'purge-history'); 1.168 + defineMethod('getScreenshot', this._getScreenshot); 1.169 + defineMethod('addNextPaintListener', this._addNextPaintListener); 1.170 + defineMethod('removeNextPaintListener', this._removeNextPaintListener); 1.171 + defineDOMRequestMethod('getCanGoBack', 'get-can-go-back'); 1.172 + defineDOMRequestMethod('getCanGoForward', 'get-can-go-forward'); 1.173 + 1.174 + let principal = this._frameElement.ownerDocument.nodePrincipal; 1.175 + let perm = Services.perms 1.176 + .testExactPermissionFromPrincipal(principal, "input-manage"); 1.177 + if (perm === Ci.nsIPermissionManager.ALLOW_ACTION) { 1.178 + defineMethod('setInputMethodActive', this._setInputMethodActive); 1.179 + } 1.180 + 1.181 + // Listen to visibilitychange on the iframe's owner window, and forward 1.182 + // changes down to the child. We want to do this while registering as few 1.183 + // visibilitychange listeners on _window as possible, because such a listener 1.184 + // may live longer than this BrowserElementParent object. 1.185 + // 1.186 + // To accomplish this, we register just one listener on the window, and have 1.187 + // it reference a WeakMap whose keys are all the BrowserElementParent objects 1.188 + // on the window. Then when the listener fires, we iterate over the 1.189 + // WeakMap's keys (which we can do, because we're chrome) to notify the 1.190 + // BrowserElementParents. 1.191 + if (!this._window._browserElementParents) { 1.192 + this._window._browserElementParents = new WeakMap(); 1.193 + this._window.addEventListener('visibilitychange', 1.194 + visibilityChangeHandler, 1.195 + /* useCapture = */ false, 1.196 + /* wantsUntrusted = */ false); 1.197 + } 1.198 + 1.199 + this._window._browserElementParents.set(this, null); 1.200 + 1.201 + // Insert ourself into the prompt service. 1.202 + BrowserElementPromptService.mapFrameToBrowserElementParent(this._frameElement, this); 1.203 + if (!isPendingFrame) { 1.204 + this._setupMessageListener(); 1.205 + this._registerAppManifest(); 1.206 + } else { 1.207 + // if we are a pending frame, we setup message manager after 1.208 + // observing remote-browser-frame-shown 1.209 + this._pendingAPICalls = []; 1.210 + Services.obs.addObserver(this, 'remote-browser-frame-shown', /* ownsWeak = */ true); 1.211 + } 1.212 +} 1.213 + 1.214 +BrowserElementParent.prototype = { 1.215 + 1.216 + QueryInterface: XPCOMUtils.generateQI([Ci.nsIObserver, 1.217 + Ci.nsISupportsWeakReference]), 1.218 + 1.219 + _runPendingAPICall: function() { 1.220 + if (!this._pendingAPICalls) { 1.221 + return; 1.222 + } 1.223 + for (let i = 0; i < this._pendingAPICalls.length; i++) { 1.224 + try { 1.225 + this._pendingAPICalls[i](); 1.226 + } catch (e) { 1.227 + // throw the expections from pending functions. 1.228 + debug('Exception when running pending API call: ' + e); 1.229 + } 1.230 + } 1.231 + delete this._pendingAPICalls; 1.232 + }, 1.233 + 1.234 + _registerAppManifest: function() { 1.235 + // If this browser represents an app then let the Webapps module register for 1.236 + // any messages that it needs. 1.237 + let appManifestURL = 1.238 + this._frameElement.QueryInterface(Ci.nsIMozBrowserFrame).appManifestURL; 1.239 + if (appManifestURL) { 1.240 + let appId = 1.241 + DOMApplicationRegistry.getAppLocalIdByManifestURL(appManifestURL); 1.242 + if (appId != Ci.nsIScriptSecurityManager.NO_APP_ID) { 1.243 + DOMApplicationRegistry.registerBrowserElementParentForApp(this, appId); 1.244 + } 1.245 + } 1.246 + }, 1.247 + 1.248 + _setupMessageListener: function() { 1.249 + this._mm = this._frameLoader.messageManager; 1.250 + let self = this; 1.251 + 1.252 + // Messages we receive are handed to functions which take a (data) argument, 1.253 + // where |data| is the message manager's data object. 1.254 + // We use a single message and dispatch to various function based 1.255 + // on data.msg_name 1.256 + let mmCalls = { 1.257 + "hello": this._recvHello, 1.258 + "contextmenu": this._fireCtxMenuEvent, 1.259 + "locationchange": this._fireEventFromMsg, 1.260 + "loadstart": this._fireEventFromMsg, 1.261 + "loadend": this._fireEventFromMsg, 1.262 + "titlechange": this._fireEventFromMsg, 1.263 + "iconchange": this._fireEventFromMsg, 1.264 + "manifestchange": this._fireEventFromMsg, 1.265 + "metachange": this._fireEventFromMsg, 1.266 + "close": this._fireEventFromMsg, 1.267 + "resize": this._fireEventFromMsg, 1.268 + "activitydone": this._fireEventFromMsg, 1.269 + "opensearch": this._fireEventFromMsg, 1.270 + "securitychange": this._fireEventFromMsg, 1.271 + "error": this._fireEventFromMsg, 1.272 + "scroll": this._fireEventFromMsg, 1.273 + "firstpaint": this._fireEventFromMsg, 1.274 + "documentfirstpaint": this._fireEventFromMsg, 1.275 + "nextpaint": this._recvNextPaint, 1.276 + "keyevent": this._fireKeyEvent, 1.277 + "showmodalprompt": this._handleShowModalPrompt, 1.278 + "got-purge-history": this._gotDOMRequestResult, 1.279 + "got-screenshot": this._gotDOMRequestResult, 1.280 + "got-can-go-back": this._gotDOMRequestResult, 1.281 + "got-can-go-forward": this._gotDOMRequestResult, 1.282 + "fullscreen-origin-change": this._remoteFullscreenOriginChange, 1.283 + "rollback-fullscreen": this._remoteFrameFullscreenReverted, 1.284 + "exit-fullscreen": this._exitFullscreen, 1.285 + "got-visible": this._gotDOMRequestResult, 1.286 + "visibilitychange": this._childVisibilityChange, 1.287 + "got-set-input-method-active": this._gotDOMRequestResult 1.288 + }; 1.289 + 1.290 + this._mm.addMessageListener('browser-element-api:call', function(aMsg) { 1.291 + if (self._isAlive() && (aMsg.data.msg_name in mmCalls)) { 1.292 + return mmCalls[aMsg.data.msg_name].apply(self, arguments); 1.293 + } 1.294 + }); 1.295 + }, 1.296 + 1.297 + /** 1.298 + * You shouldn't touch this._frameElement or this._window if _isAlive is 1.299 + * false. (You'll likely get an exception if you do.) 1.300 + */ 1.301 + _isAlive: function() { 1.302 + return !Cu.isDeadWrapper(this._frameElement) && 1.303 + !Cu.isDeadWrapper(this._frameElement.ownerDocument) && 1.304 + !Cu.isDeadWrapper(this._frameElement.ownerDocument.defaultView); 1.305 + }, 1.306 + 1.307 + get _window() { 1.308 + return this._frameElement.ownerDocument.defaultView; 1.309 + }, 1.310 + 1.311 + get _windowUtils() { 1.312 + return this._window.QueryInterface(Ci.nsIInterfaceRequestor) 1.313 + .getInterface(Ci.nsIDOMWindowUtils); 1.314 + }, 1.315 + 1.316 + promptAuth: function(authDetail, callback) { 1.317 + let evt; 1.318 + let self = this; 1.319 + let callbackCalled = false; 1.320 + let cancelCallback = function() { 1.321 + if (!callbackCalled) { 1.322 + callbackCalled = true; 1.323 + callback(false, null, null); 1.324 + } 1.325 + }; 1.326 + 1.327 + if (authDetail.isOnlyPassword) { 1.328 + // We don't handle password-only prompts, so just cancel it. 1.329 + cancelCallback(); 1.330 + return; 1.331 + } else { /* username and password */ 1.332 + let detail = { 1.333 + host: authDetail.host, 1.334 + realm: authDetail.realm 1.335 + }; 1.336 + 1.337 + evt = this._createEvent('usernameandpasswordrequired', detail, 1.338 + /* cancelable */ true); 1.339 + defineAndExpose(evt.detail, 'authenticate', function(username, password) { 1.340 + if (callbackCalled) 1.341 + return; 1.342 + callbackCalled = true; 1.343 + callback(true, username, password); 1.344 + }); 1.345 + } 1.346 + 1.347 + defineAndExpose(evt.detail, 'cancel', function() { 1.348 + cancelCallback(); 1.349 + }); 1.350 + 1.351 + this._frameElement.dispatchEvent(evt); 1.352 + 1.353 + if (!evt.defaultPrevented) { 1.354 + cancelCallback(); 1.355 + } 1.356 + }, 1.357 + 1.358 + _sendAsyncMsg: function(msg, data) { 1.359 + try { 1.360 + if (!data) { 1.361 + data = { }; 1.362 + } 1.363 + 1.364 + data.msg_name = msg; 1.365 + this._mm.sendAsyncMessage('browser-element-api:call', data); 1.366 + } catch (e) { 1.367 + return false; 1.368 + } 1.369 + return true; 1.370 + }, 1.371 + 1.372 + _recvHello: function() { 1.373 + debug("recvHello"); 1.374 + 1.375 + this._ready = true; 1.376 + 1.377 + // Inform our child if our owner element's document is invisible. Note 1.378 + // that we must do so here, rather than in the BrowserElementParent 1.379 + // constructor, because the BrowserElementChild may not be initialized when 1.380 + // we run our constructor. 1.381 + if (this._window.document.hidden) { 1.382 + this._ownerVisibilityChange(); 1.383 + } 1.384 + 1.385 + return { 1.386 + name: this._frameElement.getAttribute('name'), 1.387 + fullscreenAllowed: 1.388 + this._frameElement.hasAttribute('allowfullscreen') || 1.389 + this._frameElement.hasAttribute('mozallowfullscreen') 1.390 + }; 1.391 + }, 1.392 + 1.393 + _fireCtxMenuEvent: function(data) { 1.394 + let detail = data.json; 1.395 + let evtName = detail.msg_name; 1.396 + 1.397 + debug('fireCtxMenuEventFromMsg: ' + evtName + ' ' + detail); 1.398 + let evt = this._createEvent(evtName, detail, /* cancellable */ true); 1.399 + 1.400 + if (detail.contextmenu) { 1.401 + var self = this; 1.402 + defineAndExpose(evt.detail, 'contextMenuItemSelected', function(id) { 1.403 + self._sendAsyncMsg('fire-ctx-callback', {menuitem: id}); 1.404 + }); 1.405 + } 1.406 + 1.407 + // The embedder may have default actions on context menu events, so 1.408 + // we fire a context menu event even if the child didn't define a 1.409 + // custom context menu 1.410 + return !this._frameElement.dispatchEvent(evt); 1.411 + }, 1.412 + 1.413 + /** 1.414 + * Fire either a vanilla or a custom event, depending on the contents of 1.415 + * |data|. 1.416 + */ 1.417 + _fireEventFromMsg: function(data) { 1.418 + let detail = data.json; 1.419 + let name = detail.msg_name; 1.420 + 1.421 + // For events that send a "_payload_" property, we just want to transmit 1.422 + // this in the event. 1.423 + if ("_payload_" in detail) { 1.424 + detail = detail._payload_; 1.425 + } 1.426 + 1.427 + debug('fireEventFromMsg: ' + name + ', ' + JSON.stringify(detail)); 1.428 + let evt = this._createEvent(name, detail, 1.429 + /* cancelable = */ false); 1.430 + this._frameElement.dispatchEvent(evt); 1.431 + }, 1.432 + 1.433 + _handleShowModalPrompt: function(data) { 1.434 + // Fire a showmodalprmopt event on the iframe. When this method is called, 1.435 + // the child is spinning in a nested event loop waiting for an 1.436 + // unblock-modal-prompt message. 1.437 + // 1.438 + // If the embedder calls preventDefault() on the showmodalprompt event, 1.439 + // we'll block the child until event.detail.unblock() is called. 1.440 + // 1.441 + // Otherwise, if preventDefault() is not called, we'll send the 1.442 + // unblock-modal-prompt message to the child as soon as the event is done 1.443 + // dispatching. 1.444 + 1.445 + let detail = data.json; 1.446 + debug('handleShowPrompt ' + JSON.stringify(detail)); 1.447 + 1.448 + // Strip off the windowID property from the object we send along in the 1.449 + // event. 1.450 + let windowID = detail.windowID; 1.451 + delete detail.windowID; 1.452 + debug("Event will have detail: " + JSON.stringify(detail)); 1.453 + let evt = this._createEvent('showmodalprompt', detail, 1.454 + /* cancelable = */ true); 1.455 + 1.456 + let self = this; 1.457 + let unblockMsgSent = false; 1.458 + function sendUnblockMsg() { 1.459 + if (unblockMsgSent) { 1.460 + return; 1.461 + } 1.462 + unblockMsgSent = true; 1.463 + 1.464 + // We don't need to sanitize evt.detail.returnValue (e.g. converting the 1.465 + // return value of confirm() to a boolean); Gecko does that for us. 1.466 + 1.467 + let data = { windowID: windowID, 1.468 + returnValue: evt.detail.returnValue }; 1.469 + self._sendAsyncMsg('unblock-modal-prompt', data); 1.470 + } 1.471 + 1.472 + defineAndExpose(evt.detail, 'unblock', function() { 1.473 + sendUnblockMsg(); 1.474 + }); 1.475 + 1.476 + this._frameElement.dispatchEvent(evt); 1.477 + 1.478 + if (!evt.defaultPrevented) { 1.479 + // Unblock the inner frame immediately. Otherwise we'll unblock upon 1.480 + // evt.detail.unblock(). 1.481 + sendUnblockMsg(); 1.482 + } 1.483 + }, 1.484 + 1.485 + _createEvent: function(evtName, detail, cancelable) { 1.486 + // This will have to change if we ever want to send a CustomEvent with null 1.487 + // detail. For now, it's OK. 1.488 + if (detail !== undefined && detail !== null) { 1.489 + exposeAll(detail); 1.490 + return new this._window.CustomEvent('mozbrowser' + evtName, 1.491 + { bubbles: true, 1.492 + cancelable: cancelable, 1.493 + detail: detail }); 1.494 + } 1.495 + 1.496 + return new this._window.Event('mozbrowser' + evtName, 1.497 + { bubbles: true, 1.498 + cancelable: cancelable }); 1.499 + }, 1.500 + 1.501 + /** 1.502 + * If remote frame haven't been set up, we enqueue a function that get a 1.503 + * DOMRequest until the remote frame is ready and return another DOMRequest 1.504 + * to caller. When we get the real DOMRequest, we will help forward the 1.505 + * success/error callback to the DOMRequest that caller got. 1.506 + */ 1.507 + _queueDOMRequest: function(msgName, args) { 1.508 + if (!this._pendingAPICalls) { 1.509 + return; 1.510 + } 1.511 + 1.512 + let req = Services.DOMRequest.createRequest(this._window); 1.513 + let self = this; 1.514 + let getRealDOMRequest = function() { 1.515 + let realReq = self._sendDOMRequest(msgName, args); 1.516 + realReq.onsuccess = function(v) { 1.517 + Services.DOMRequest.fireSuccess(req, v); 1.518 + }; 1.519 + realReq.onerror = function(v) { 1.520 + Services.DOMRequest.fireError(req, v); 1.521 + }; 1.522 + }; 1.523 + this._pendingAPICalls.push(getRealDOMRequest); 1.524 + return req; 1.525 + }, 1.526 + 1.527 + /** 1.528 + * Kick off a DOMRequest in the child process. 1.529 + * 1.530 + * We'll fire an event called |msgName| on the child process, passing along 1.531 + * an object with two fields: 1.532 + * 1.533 + * - id: the ID of this request. 1.534 + * - arg: arguments to pass to the child along with this request. 1.535 + * 1.536 + * We expect the child to pass the ID back to us upon completion of the 1.537 + * request. See _gotDOMRequestResult. 1.538 + */ 1.539 + _sendDOMRequest: function(msgName, args) { 1.540 + let id = 'req_' + this._domRequestCounter++; 1.541 + let req = Services.DOMRequest.createRequest(this._window); 1.542 + if (this._sendAsyncMsg(msgName, {id: id, args: args})) { 1.543 + this._pendingDOMRequests[id] = req; 1.544 + } else { 1.545 + Services.DOMRequest.fireErrorAsync(req, "fail"); 1.546 + } 1.547 + return req; 1.548 + }, 1.549 + 1.550 + /** 1.551 + * Called when the child process finishes handling a DOMRequest. data.json 1.552 + * must have the fields [id, successRv], if the DOMRequest was successful, or 1.553 + * [id, errorMsg], if the request was not successful. 1.554 + * 1.555 + * The fields have the following meanings: 1.556 + * 1.557 + * - id: the ID of the DOM request (see _sendDOMRequest) 1.558 + * - successRv: the request's return value, if the request succeeded 1.559 + * - errorMsg: the message to pass to DOMRequest.fireError(), if the request 1.560 + * failed. 1.561 + * 1.562 + */ 1.563 + _gotDOMRequestResult: function(data) { 1.564 + let req = this._pendingDOMRequests[data.json.id]; 1.565 + delete this._pendingDOMRequests[data.json.id]; 1.566 + 1.567 + if ('successRv' in data.json) { 1.568 + debug("Successful gotDOMRequestResult."); 1.569 + Services.DOMRequest.fireSuccess(req, data.json.successRv); 1.570 + } 1.571 + else { 1.572 + debug("Got error in gotDOMRequestResult."); 1.573 + Services.DOMRequest.fireErrorAsync(req, data.json.errorMsg); 1.574 + } 1.575 + }, 1.576 + 1.577 + _setVisible: function(visible) { 1.578 + this._sendAsyncMsg('set-visible', {visible: visible}); 1.579 + this._frameLoader.visible = visible; 1.580 + }, 1.581 + 1.582 + _sendMouseEvent: function(type, x, y, button, clickCount, modifiers) { 1.583 + this._sendAsyncMsg("send-mouse-event", { 1.584 + "type": type, 1.585 + "x": x, 1.586 + "y": y, 1.587 + "button": button, 1.588 + "clickCount": clickCount, 1.589 + "modifiers": modifiers 1.590 + }); 1.591 + }, 1.592 + 1.593 + _sendTouchEvent: function(type, identifiers, touchesX, touchesY, 1.594 + radiisX, radiisY, rotationAngles, forces, 1.595 + count, modifiers) { 1.596 + 1.597 + let tabParent = this._frameLoader.tabParent; 1.598 + if (tabParent && tabParent.useAsyncPanZoom) { 1.599 + tabParent.injectTouchEvent(type, 1.600 + identifiers, 1.601 + touchesX, 1.602 + touchesY, 1.603 + radiisX, 1.604 + radiisY, 1.605 + rotationAngles, 1.606 + forces, 1.607 + count, 1.608 + modifiers); 1.609 + } else { 1.610 + this._sendAsyncMsg("send-touch-event", { 1.611 + "type": type, 1.612 + "identifiers": identifiers, 1.613 + "touchesX": touchesX, 1.614 + "touchesY": touchesY, 1.615 + "radiisX": radiisX, 1.616 + "radiisY": radiisY, 1.617 + "rotationAngles": rotationAngles, 1.618 + "forces": forces, 1.619 + "count": count, 1.620 + "modifiers": modifiers 1.621 + }); 1.622 + } 1.623 + }, 1.624 + 1.625 + _goBack: function() { 1.626 + this._sendAsyncMsg('go-back'); 1.627 + }, 1.628 + 1.629 + _goForward: function() { 1.630 + this._sendAsyncMsg('go-forward'); 1.631 + }, 1.632 + 1.633 + _reload: function(hardReload) { 1.634 + this._sendAsyncMsg('reload', {hardReload: hardReload}); 1.635 + }, 1.636 + 1.637 + _stop: function() { 1.638 + this._sendAsyncMsg('stop'); 1.639 + }, 1.640 + 1.641 + _getScreenshot: function(_width, _height, _mimeType) { 1.642 + let width = parseInt(_width); 1.643 + let height = parseInt(_height); 1.644 + let mimeType = (typeof _mimeType === 'string') ? 1.645 + _mimeType.trim().toLowerCase() : 'image/jpeg'; 1.646 + if (isNaN(width) || isNaN(height) || width < 0 || height < 0) { 1.647 + throw Components.Exception("Invalid argument", 1.648 + Cr.NS_ERROR_INVALID_ARG); 1.649 + } 1.650 + 1.651 + if (!this._mm) { 1.652 + // Child haven't been loaded. 1.653 + return this._queueDOMRequest('get-screenshot', 1.654 + {width: width, height: height, 1.655 + mimeType: mimeType}); 1.656 + } 1.657 + 1.658 + return this._sendDOMRequest('get-screenshot', 1.659 + {width: width, height: height, 1.660 + mimeType: mimeType}); 1.661 + }, 1.662 + 1.663 + _recvNextPaint: function(data) { 1.664 + let listeners = this._nextPaintListeners; 1.665 + this._nextPaintListeners = []; 1.666 + for (let listener of listeners) { 1.667 + try { 1.668 + listener(); 1.669 + } catch (e) { 1.670 + // If a listener throws we'll continue. 1.671 + } 1.672 + } 1.673 + }, 1.674 + 1.675 + _addNextPaintListener: function(listener) { 1.676 + if (typeof listener != 'function') 1.677 + throw Components.Exception("Invalid argument", Cr.NS_ERROR_INVALID_ARG); 1.678 + 1.679 + let self = this; 1.680 + let run = function() { 1.681 + if (self._nextPaintListeners.push(listener) == 1) 1.682 + self._sendAsyncMsg('activate-next-paint-listener'); 1.683 + }; 1.684 + if (!this._mm) { 1.685 + this._pendingAPICalls.push(run); 1.686 + } else { 1.687 + run(); 1.688 + } 1.689 + }, 1.690 + 1.691 + _removeNextPaintListener: function(listener) { 1.692 + if (typeof listener != 'function') 1.693 + throw Components.Exception("Invalid argument", Cr.NS_ERROR_INVALID_ARG); 1.694 + 1.695 + let self = this; 1.696 + let run = function() { 1.697 + for (let i = self._nextPaintListeners.length - 1; i >= 0; i--) { 1.698 + if (self._nextPaintListeners[i] == listener) { 1.699 + self._nextPaintListeners.splice(i, 1); 1.700 + break; 1.701 + } 1.702 + } 1.703 + 1.704 + if (self._nextPaintListeners.length == 0) 1.705 + self._sendAsyncMsg('deactivate-next-paint-listener'); 1.706 + }; 1.707 + if (!this._mm) { 1.708 + this._pendingAPICalls.push(run); 1.709 + } else { 1.710 + run(); 1.711 + } 1.712 + }, 1.713 + 1.714 + _setInputMethodActive: function(isActive) { 1.715 + if (typeof isActive !== 'boolean') { 1.716 + throw Components.Exception("Invalid argument", 1.717 + Cr.NS_ERROR_INVALID_ARG); 1.718 + } 1.719 + 1.720 + let req = Services.DOMRequest.createRequest(this._window); 1.721 + 1.722 + // Deactivate the old input method if needed. 1.723 + if (activeInputFrame && isActive) { 1.724 + if (Cu.isDeadWrapper(activeInputFrame)) { 1.725 + // If the activeInputFrame is already a dead object, 1.726 + // we should simply set it to null directly. 1.727 + activeInputFrame = null; 1.728 + this._sendSetInputMethodActiveDOMRequest(req, isActive); 1.729 + } else { 1.730 + let reqOld = XPCNativeWrapper.unwrap(activeInputFrame) 1.731 + .setInputMethodActive(false); 1.732 + 1.733 + // We wan't to continue regardless whether this req succeeded 1.734 + reqOld.onsuccess = reqOld.onerror = function() { 1.735 + let setActive = function() { 1.736 + activeInputFrame = null; 1.737 + this._sendSetInputMethodActiveDOMRequest(req, isActive); 1.738 + }.bind(this); 1.739 + 1.740 + if (this._ready) { 1.741 + setActive(); 1.742 + return; 1.743 + } 1.744 + 1.745 + // Wait for the hello event from BrowserElementChild 1.746 + let onReady = function(aMsg) { 1.747 + if (this._isAlive() && (aMsg.data.msg_name === 'hello')) { 1.748 + setActive(); 1.749 + 1.750 + this._mm.removeMessageListener('browser-element-api:call', 1.751 + onReady); 1.752 + } 1.753 + }.bind(this); 1.754 + 1.755 + this._mm.addMessageListener('browser-element-api:call', onReady); 1.756 + }.bind(this); 1.757 + } 1.758 + } else { 1.759 + this._sendSetInputMethodActiveDOMRequest(req, isActive); 1.760 + } 1.761 + return req; 1.762 + }, 1.763 + 1.764 + _sendSetInputMethodActiveDOMRequest: function(req, isActive) { 1.765 + let id = 'req_' + this._domRequestCounter++; 1.766 + let data = { 1.767 + id : id, 1.768 + args: { isActive: isActive } 1.769 + }; 1.770 + if (this._sendAsyncMsg('set-input-method-active', data)) { 1.771 + activeInputFrame = this._frameElement; 1.772 + this._pendingDOMRequests[id] = req; 1.773 + } else { 1.774 + Services.DOMRequest.fireErrorAsync(req, 'fail'); 1.775 + } 1.776 + }, 1.777 + 1.778 + _fireKeyEvent: function(data) { 1.779 + let evt = this._window.document.createEvent("KeyboardEvent"); 1.780 + evt.initKeyEvent(data.json.type, true, true, this._window, 1.781 + false, false, false, false, // modifiers 1.782 + data.json.keyCode, 1.783 + data.json.charCode); 1.784 + 1.785 + this._frameElement.dispatchEvent(evt); 1.786 + }, 1.787 + 1.788 + /** 1.789 + * Called when the visibility of the window which owns this iframe changes. 1.790 + */ 1.791 + _ownerVisibilityChange: function() { 1.792 + this._sendAsyncMsg('owner-visibility-change', 1.793 + {visible: !this._window.document.hidden}); 1.794 + }, 1.795 + 1.796 + /* 1.797 + * Called when the child notices that its visibility has changed. 1.798 + * 1.799 + * This is sometimes redundant; for example, the child's visibility may 1.800 + * change in response to a setVisible request that we made here! But it's 1.801 + * not always redundant; for example, the child's visibility may change in 1.802 + * response to its parent docshell being hidden. 1.803 + */ 1.804 + _childVisibilityChange: function(data) { 1.805 + debug("_childVisibilityChange(" + data.json.visible + ")"); 1.806 + this._frameLoader.visible = data.json.visible; 1.807 + 1.808 + this._fireEventFromMsg(data); 1.809 + }, 1.810 + 1.811 + _exitFullscreen: function() { 1.812 + this._windowUtils.exitFullscreen(); 1.813 + }, 1.814 + 1.815 + _remoteFullscreenOriginChange: function(data) { 1.816 + let origin = data.json._payload_; 1.817 + this._windowUtils.remoteFrameFullscreenChanged(this._frameElement, origin); 1.818 + }, 1.819 + 1.820 + _remoteFrameFullscreenReverted: function(data) { 1.821 + this._windowUtils.remoteFrameFullscreenReverted(); 1.822 + }, 1.823 + 1.824 + _fireFatalError: function() { 1.825 + let evt = this._createEvent('error', {type: 'fatal'}, 1.826 + /* cancelable = */ false); 1.827 + this._frameElement.dispatchEvent(evt); 1.828 + }, 1.829 + 1.830 + observe: function(subject, topic, data) { 1.831 + switch(topic) { 1.832 + case 'oop-frameloader-crashed': 1.833 + if (this._isAlive() && subject == this._frameLoader) { 1.834 + this._fireFatalError(); 1.835 + } 1.836 + break; 1.837 + case 'ask-children-to-exit-fullscreen': 1.838 + if (this._isAlive() && 1.839 + this._frameElement.ownerDocument == subject && 1.840 + this._hasRemoteFrame) { 1.841 + this._sendAsyncMsg('exit-fullscreen'); 1.842 + } 1.843 + break; 1.844 + case 'remote-browser-frame-shown': 1.845 + if (this._frameLoader == subject) { 1.846 + if (!this._mm) { 1.847 + this._setupMessageListener(); 1.848 + this._registerAppManifest(); 1.849 + this._runPendingAPICall(); 1.850 + } 1.851 + Services.obs.removeObserver(this, 'remote-browser-frame-shown'); 1.852 + } 1.853 + default: 1.854 + debug('Unknown topic: ' + topic); 1.855 + break; 1.856 + }; 1.857 + }, 1.858 +};