dom/browser-element/BrowserElementParent.jsm

changeset 2
7e26c7da4463
equal deleted inserted replaced
-1:000000000000 0:2994f4aa0406
1 /* This Source Code Form is subject to the terms of the Mozilla Public
2 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
3 * You can obtain one at http://mozilla.org/MPL/2.0/. */
4
5 "use strict";
6
7 let Cu = Components.utils;
8 let Ci = Components.interfaces;
9 let Cc = Components.classes;
10 let Cr = Components.results;
11
12 /* BrowserElementParent injects script to listen for certain events in the
13 * child. We then listen to messages from the child script and take
14 * appropriate action here in the parent.
15 */
16
17 this.EXPORTED_SYMBOLS = ["BrowserElementParentBuilder"];
18
19 Cu.import("resource://gre/modules/Services.jsm");
20 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
21 Cu.import("resource://gre/modules/BrowserElementPromptService.jsm");
22
23 XPCOMUtils.defineLazyGetter(this, "DOMApplicationRegistry", function () {
24 Cu.import("resource://gre/modules/Webapps.jsm");
25 return DOMApplicationRegistry;
26 });
27
28 const TOUCH_EVENTS_ENABLED_PREF = "dom.w3c_touch_events.enabled";
29
30 function debug(msg) {
31 //dump("BrowserElementParent.jsm - " + msg + "\n");
32 }
33
34 function getIntPref(prefName, def) {
35 try {
36 return Services.prefs.getIntPref(prefName);
37 }
38 catch(err) {
39 return def;
40 }
41 }
42
43 function exposeAll(obj) {
44 // Filter for Objects and Arrays.
45 if (typeof obj !== "object" || !obj)
46 return;
47
48 // Recursively expose our children.
49 Object.keys(obj).forEach(function(key) {
50 exposeAll(obj[key]);
51 });
52
53 // If we're not an Array, generate an __exposedProps__ object for ourselves.
54 if (obj instanceof Array)
55 return;
56 var exposed = {};
57 Object.keys(obj).forEach(function(key) {
58 exposed[key] = 'rw';
59 });
60 obj.__exposedProps__ = exposed;
61 }
62
63 function defineAndExpose(obj, name, value) {
64 obj[name] = value;
65 if (!('__exposedProps__' in obj))
66 obj.__exposedProps__ = {};
67 obj.__exposedProps__[name] = 'r';
68 }
69
70 function visibilityChangeHandler(e) {
71 // The visibilitychange event's target is the document.
72 let win = e.target.defaultView;
73
74 if (!win._browserElementParents) {
75 return;
76 }
77
78 let beps = Cu.nondeterministicGetWeakMapKeys(win._browserElementParents);
79 if (beps.length == 0) {
80 win.removeEventListener('visibilitychange', visibilityChangeHandler);
81 return;
82 }
83
84 for (let i = 0; i < beps.length; i++) {
85 beps[i]._ownerVisibilityChange();
86 }
87 }
88
89 this.BrowserElementParentBuilder = {
90 create: function create(frameLoader, hasRemoteFrame, isPendingFrame) {
91 return new BrowserElementParent(frameLoader, hasRemoteFrame);
92 }
93 }
94
95
96 // The active input method iframe.
97 let activeInputFrame = null;
98
99 function BrowserElementParent(frameLoader, hasRemoteFrame, isPendingFrame) {
100 debug("Creating new BrowserElementParent object for " + frameLoader);
101 this._domRequestCounter = 0;
102 this._pendingDOMRequests = {};
103 this._hasRemoteFrame = hasRemoteFrame;
104 this._nextPaintListeners = [];
105
106 this._frameLoader = frameLoader;
107 this._frameElement = frameLoader.QueryInterface(Ci.nsIFrameLoader).ownerElement;
108 let self = this;
109 if (!this._frameElement) {
110 debug("No frame element?");
111 return;
112 }
113
114 Services.obs.addObserver(this, 'ask-children-to-exit-fullscreen', /* ownsWeak = */ true);
115 Services.obs.addObserver(this, 'oop-frameloader-crashed', /* ownsWeak = */ true);
116
117 let defineMethod = function(name, fn) {
118 XPCNativeWrapper.unwrap(self._frameElement)[name] = function() {
119 if (self._isAlive()) {
120 return fn.apply(self, arguments);
121 }
122 };
123 }
124
125 let defineNoReturnMethod = function(name, fn) {
126 XPCNativeWrapper.unwrap(self._frameElement)[name] = function method() {
127 if (!self._mm) {
128 // Remote browser haven't been created, we just queue the API call.
129 let args = Array.slice(arguments);
130 args.unshift(self);
131 self._pendingAPICalls.push(method.bind.apply(fn, args));
132 return;
133 }
134 if (self._isAlive()) {
135 fn.apply(self, arguments);
136 }
137 };
138 };
139
140 let defineDOMRequestMethod = function(domName, msgName) {
141 XPCNativeWrapper.unwrap(self._frameElement)[domName] = function() {
142 if (!self._mm) {
143 return self._queueDOMRequest;
144 }
145 if (self._isAlive()) {
146 return self._sendDOMRequest(msgName);
147 }
148 };
149 }
150
151 // Define methods on the frame element.
152 defineNoReturnMethod('setVisible', this._setVisible);
153 defineDOMRequestMethod('getVisible', 'get-visible');
154 defineNoReturnMethod('sendMouseEvent', this._sendMouseEvent);
155
156 // 0 = disabled, 1 = enabled, 2 - auto detect
157 if (getIntPref(TOUCH_EVENTS_ENABLED_PREF, 0) != 0) {
158 defineNoReturnMethod('sendTouchEvent', this._sendTouchEvent);
159 }
160 defineNoReturnMethod('goBack', this._goBack);
161 defineNoReturnMethod('goForward', this._goForward);
162 defineNoReturnMethod('reload', this._reload);
163 defineNoReturnMethod('stop', this._stop);
164 defineDOMRequestMethod('purgeHistory', 'purge-history');
165 defineMethod('getScreenshot', this._getScreenshot);
166 defineMethod('addNextPaintListener', this._addNextPaintListener);
167 defineMethod('removeNextPaintListener', this._removeNextPaintListener);
168 defineDOMRequestMethod('getCanGoBack', 'get-can-go-back');
169 defineDOMRequestMethod('getCanGoForward', 'get-can-go-forward');
170
171 let principal = this._frameElement.ownerDocument.nodePrincipal;
172 let perm = Services.perms
173 .testExactPermissionFromPrincipal(principal, "input-manage");
174 if (perm === Ci.nsIPermissionManager.ALLOW_ACTION) {
175 defineMethod('setInputMethodActive', this._setInputMethodActive);
176 }
177
178 // Listen to visibilitychange on the iframe's owner window, and forward
179 // changes down to the child. We want to do this while registering as few
180 // visibilitychange listeners on _window as possible, because such a listener
181 // may live longer than this BrowserElementParent object.
182 //
183 // To accomplish this, we register just one listener on the window, and have
184 // it reference a WeakMap whose keys are all the BrowserElementParent objects
185 // on the window. Then when the listener fires, we iterate over the
186 // WeakMap's keys (which we can do, because we're chrome) to notify the
187 // BrowserElementParents.
188 if (!this._window._browserElementParents) {
189 this._window._browserElementParents = new WeakMap();
190 this._window.addEventListener('visibilitychange',
191 visibilityChangeHandler,
192 /* useCapture = */ false,
193 /* wantsUntrusted = */ false);
194 }
195
196 this._window._browserElementParents.set(this, null);
197
198 // Insert ourself into the prompt service.
199 BrowserElementPromptService.mapFrameToBrowserElementParent(this._frameElement, this);
200 if (!isPendingFrame) {
201 this._setupMessageListener();
202 this._registerAppManifest();
203 } else {
204 // if we are a pending frame, we setup message manager after
205 // observing remote-browser-frame-shown
206 this._pendingAPICalls = [];
207 Services.obs.addObserver(this, 'remote-browser-frame-shown', /* ownsWeak = */ true);
208 }
209 }
210
211 BrowserElementParent.prototype = {
212
213 QueryInterface: XPCOMUtils.generateQI([Ci.nsIObserver,
214 Ci.nsISupportsWeakReference]),
215
216 _runPendingAPICall: function() {
217 if (!this._pendingAPICalls) {
218 return;
219 }
220 for (let i = 0; i < this._pendingAPICalls.length; i++) {
221 try {
222 this._pendingAPICalls[i]();
223 } catch (e) {
224 // throw the expections from pending functions.
225 debug('Exception when running pending API call: ' + e);
226 }
227 }
228 delete this._pendingAPICalls;
229 },
230
231 _registerAppManifest: function() {
232 // If this browser represents an app then let the Webapps module register for
233 // any messages that it needs.
234 let appManifestURL =
235 this._frameElement.QueryInterface(Ci.nsIMozBrowserFrame).appManifestURL;
236 if (appManifestURL) {
237 let appId =
238 DOMApplicationRegistry.getAppLocalIdByManifestURL(appManifestURL);
239 if (appId != Ci.nsIScriptSecurityManager.NO_APP_ID) {
240 DOMApplicationRegistry.registerBrowserElementParentForApp(this, appId);
241 }
242 }
243 },
244
245 _setupMessageListener: function() {
246 this._mm = this._frameLoader.messageManager;
247 let self = this;
248
249 // Messages we receive are handed to functions which take a (data) argument,
250 // where |data| is the message manager's data object.
251 // We use a single message and dispatch to various function based
252 // on data.msg_name
253 let mmCalls = {
254 "hello": this._recvHello,
255 "contextmenu": this._fireCtxMenuEvent,
256 "locationchange": this._fireEventFromMsg,
257 "loadstart": this._fireEventFromMsg,
258 "loadend": this._fireEventFromMsg,
259 "titlechange": this._fireEventFromMsg,
260 "iconchange": this._fireEventFromMsg,
261 "manifestchange": this._fireEventFromMsg,
262 "metachange": this._fireEventFromMsg,
263 "close": this._fireEventFromMsg,
264 "resize": this._fireEventFromMsg,
265 "activitydone": this._fireEventFromMsg,
266 "opensearch": this._fireEventFromMsg,
267 "securitychange": this._fireEventFromMsg,
268 "error": this._fireEventFromMsg,
269 "scroll": this._fireEventFromMsg,
270 "firstpaint": this._fireEventFromMsg,
271 "documentfirstpaint": this._fireEventFromMsg,
272 "nextpaint": this._recvNextPaint,
273 "keyevent": this._fireKeyEvent,
274 "showmodalprompt": this._handleShowModalPrompt,
275 "got-purge-history": this._gotDOMRequestResult,
276 "got-screenshot": this._gotDOMRequestResult,
277 "got-can-go-back": this._gotDOMRequestResult,
278 "got-can-go-forward": this._gotDOMRequestResult,
279 "fullscreen-origin-change": this._remoteFullscreenOriginChange,
280 "rollback-fullscreen": this._remoteFrameFullscreenReverted,
281 "exit-fullscreen": this._exitFullscreen,
282 "got-visible": this._gotDOMRequestResult,
283 "visibilitychange": this._childVisibilityChange,
284 "got-set-input-method-active": this._gotDOMRequestResult
285 };
286
287 this._mm.addMessageListener('browser-element-api:call', function(aMsg) {
288 if (self._isAlive() && (aMsg.data.msg_name in mmCalls)) {
289 return mmCalls[aMsg.data.msg_name].apply(self, arguments);
290 }
291 });
292 },
293
294 /**
295 * You shouldn't touch this._frameElement or this._window if _isAlive is
296 * false. (You'll likely get an exception if you do.)
297 */
298 _isAlive: function() {
299 return !Cu.isDeadWrapper(this._frameElement) &&
300 !Cu.isDeadWrapper(this._frameElement.ownerDocument) &&
301 !Cu.isDeadWrapper(this._frameElement.ownerDocument.defaultView);
302 },
303
304 get _window() {
305 return this._frameElement.ownerDocument.defaultView;
306 },
307
308 get _windowUtils() {
309 return this._window.QueryInterface(Ci.nsIInterfaceRequestor)
310 .getInterface(Ci.nsIDOMWindowUtils);
311 },
312
313 promptAuth: function(authDetail, callback) {
314 let evt;
315 let self = this;
316 let callbackCalled = false;
317 let cancelCallback = function() {
318 if (!callbackCalled) {
319 callbackCalled = true;
320 callback(false, null, null);
321 }
322 };
323
324 if (authDetail.isOnlyPassword) {
325 // We don't handle password-only prompts, so just cancel it.
326 cancelCallback();
327 return;
328 } else { /* username and password */
329 let detail = {
330 host: authDetail.host,
331 realm: authDetail.realm
332 };
333
334 evt = this._createEvent('usernameandpasswordrequired', detail,
335 /* cancelable */ true);
336 defineAndExpose(evt.detail, 'authenticate', function(username, password) {
337 if (callbackCalled)
338 return;
339 callbackCalled = true;
340 callback(true, username, password);
341 });
342 }
343
344 defineAndExpose(evt.detail, 'cancel', function() {
345 cancelCallback();
346 });
347
348 this._frameElement.dispatchEvent(evt);
349
350 if (!evt.defaultPrevented) {
351 cancelCallback();
352 }
353 },
354
355 _sendAsyncMsg: function(msg, data) {
356 try {
357 if (!data) {
358 data = { };
359 }
360
361 data.msg_name = msg;
362 this._mm.sendAsyncMessage('browser-element-api:call', data);
363 } catch (e) {
364 return false;
365 }
366 return true;
367 },
368
369 _recvHello: function() {
370 debug("recvHello");
371
372 this._ready = true;
373
374 // Inform our child if our owner element's document is invisible. Note
375 // that we must do so here, rather than in the BrowserElementParent
376 // constructor, because the BrowserElementChild may not be initialized when
377 // we run our constructor.
378 if (this._window.document.hidden) {
379 this._ownerVisibilityChange();
380 }
381
382 return {
383 name: this._frameElement.getAttribute('name'),
384 fullscreenAllowed:
385 this._frameElement.hasAttribute('allowfullscreen') ||
386 this._frameElement.hasAttribute('mozallowfullscreen')
387 };
388 },
389
390 _fireCtxMenuEvent: function(data) {
391 let detail = data.json;
392 let evtName = detail.msg_name;
393
394 debug('fireCtxMenuEventFromMsg: ' + evtName + ' ' + detail);
395 let evt = this._createEvent(evtName, detail, /* cancellable */ true);
396
397 if (detail.contextmenu) {
398 var self = this;
399 defineAndExpose(evt.detail, 'contextMenuItemSelected', function(id) {
400 self._sendAsyncMsg('fire-ctx-callback', {menuitem: id});
401 });
402 }
403
404 // The embedder may have default actions on context menu events, so
405 // we fire a context menu event even if the child didn't define a
406 // custom context menu
407 return !this._frameElement.dispatchEvent(evt);
408 },
409
410 /**
411 * Fire either a vanilla or a custom event, depending on the contents of
412 * |data|.
413 */
414 _fireEventFromMsg: function(data) {
415 let detail = data.json;
416 let name = detail.msg_name;
417
418 // For events that send a "_payload_" property, we just want to transmit
419 // this in the event.
420 if ("_payload_" in detail) {
421 detail = detail._payload_;
422 }
423
424 debug('fireEventFromMsg: ' + name + ', ' + JSON.stringify(detail));
425 let evt = this._createEvent(name, detail,
426 /* cancelable = */ false);
427 this._frameElement.dispatchEvent(evt);
428 },
429
430 _handleShowModalPrompt: function(data) {
431 // Fire a showmodalprmopt event on the iframe. When this method is called,
432 // the child is spinning in a nested event loop waiting for an
433 // unblock-modal-prompt message.
434 //
435 // If the embedder calls preventDefault() on the showmodalprompt event,
436 // we'll block the child until event.detail.unblock() is called.
437 //
438 // Otherwise, if preventDefault() is not called, we'll send the
439 // unblock-modal-prompt message to the child as soon as the event is done
440 // dispatching.
441
442 let detail = data.json;
443 debug('handleShowPrompt ' + JSON.stringify(detail));
444
445 // Strip off the windowID property from the object we send along in the
446 // event.
447 let windowID = detail.windowID;
448 delete detail.windowID;
449 debug("Event will have detail: " + JSON.stringify(detail));
450 let evt = this._createEvent('showmodalprompt', detail,
451 /* cancelable = */ true);
452
453 let self = this;
454 let unblockMsgSent = false;
455 function sendUnblockMsg() {
456 if (unblockMsgSent) {
457 return;
458 }
459 unblockMsgSent = true;
460
461 // We don't need to sanitize evt.detail.returnValue (e.g. converting the
462 // return value of confirm() to a boolean); Gecko does that for us.
463
464 let data = { windowID: windowID,
465 returnValue: evt.detail.returnValue };
466 self._sendAsyncMsg('unblock-modal-prompt', data);
467 }
468
469 defineAndExpose(evt.detail, 'unblock', function() {
470 sendUnblockMsg();
471 });
472
473 this._frameElement.dispatchEvent(evt);
474
475 if (!evt.defaultPrevented) {
476 // Unblock the inner frame immediately. Otherwise we'll unblock upon
477 // evt.detail.unblock().
478 sendUnblockMsg();
479 }
480 },
481
482 _createEvent: function(evtName, detail, cancelable) {
483 // This will have to change if we ever want to send a CustomEvent with null
484 // detail. For now, it's OK.
485 if (detail !== undefined && detail !== null) {
486 exposeAll(detail);
487 return new this._window.CustomEvent('mozbrowser' + evtName,
488 { bubbles: true,
489 cancelable: cancelable,
490 detail: detail });
491 }
492
493 return new this._window.Event('mozbrowser' + evtName,
494 { bubbles: true,
495 cancelable: cancelable });
496 },
497
498 /**
499 * If remote frame haven't been set up, we enqueue a function that get a
500 * DOMRequest until the remote frame is ready and return another DOMRequest
501 * to caller. When we get the real DOMRequest, we will help forward the
502 * success/error callback to the DOMRequest that caller got.
503 */
504 _queueDOMRequest: function(msgName, args) {
505 if (!this._pendingAPICalls) {
506 return;
507 }
508
509 let req = Services.DOMRequest.createRequest(this._window);
510 let self = this;
511 let getRealDOMRequest = function() {
512 let realReq = self._sendDOMRequest(msgName, args);
513 realReq.onsuccess = function(v) {
514 Services.DOMRequest.fireSuccess(req, v);
515 };
516 realReq.onerror = function(v) {
517 Services.DOMRequest.fireError(req, v);
518 };
519 };
520 this._pendingAPICalls.push(getRealDOMRequest);
521 return req;
522 },
523
524 /**
525 * Kick off a DOMRequest in the child process.
526 *
527 * We'll fire an event called |msgName| on the child process, passing along
528 * an object with two fields:
529 *
530 * - id: the ID of this request.
531 * - arg: arguments to pass to the child along with this request.
532 *
533 * We expect the child to pass the ID back to us upon completion of the
534 * request. See _gotDOMRequestResult.
535 */
536 _sendDOMRequest: function(msgName, args) {
537 let id = 'req_' + this._domRequestCounter++;
538 let req = Services.DOMRequest.createRequest(this._window);
539 if (this._sendAsyncMsg(msgName, {id: id, args: args})) {
540 this._pendingDOMRequests[id] = req;
541 } else {
542 Services.DOMRequest.fireErrorAsync(req, "fail");
543 }
544 return req;
545 },
546
547 /**
548 * Called when the child process finishes handling a DOMRequest. data.json
549 * must have the fields [id, successRv], if the DOMRequest was successful, or
550 * [id, errorMsg], if the request was not successful.
551 *
552 * The fields have the following meanings:
553 *
554 * - id: the ID of the DOM request (see _sendDOMRequest)
555 * - successRv: the request's return value, if the request succeeded
556 * - errorMsg: the message to pass to DOMRequest.fireError(), if the request
557 * failed.
558 *
559 */
560 _gotDOMRequestResult: function(data) {
561 let req = this._pendingDOMRequests[data.json.id];
562 delete this._pendingDOMRequests[data.json.id];
563
564 if ('successRv' in data.json) {
565 debug("Successful gotDOMRequestResult.");
566 Services.DOMRequest.fireSuccess(req, data.json.successRv);
567 }
568 else {
569 debug("Got error in gotDOMRequestResult.");
570 Services.DOMRequest.fireErrorAsync(req, data.json.errorMsg);
571 }
572 },
573
574 _setVisible: function(visible) {
575 this._sendAsyncMsg('set-visible', {visible: visible});
576 this._frameLoader.visible = visible;
577 },
578
579 _sendMouseEvent: function(type, x, y, button, clickCount, modifiers) {
580 this._sendAsyncMsg("send-mouse-event", {
581 "type": type,
582 "x": x,
583 "y": y,
584 "button": button,
585 "clickCount": clickCount,
586 "modifiers": modifiers
587 });
588 },
589
590 _sendTouchEvent: function(type, identifiers, touchesX, touchesY,
591 radiisX, radiisY, rotationAngles, forces,
592 count, modifiers) {
593
594 let tabParent = this._frameLoader.tabParent;
595 if (tabParent && tabParent.useAsyncPanZoom) {
596 tabParent.injectTouchEvent(type,
597 identifiers,
598 touchesX,
599 touchesY,
600 radiisX,
601 radiisY,
602 rotationAngles,
603 forces,
604 count,
605 modifiers);
606 } else {
607 this._sendAsyncMsg("send-touch-event", {
608 "type": type,
609 "identifiers": identifiers,
610 "touchesX": touchesX,
611 "touchesY": touchesY,
612 "radiisX": radiisX,
613 "radiisY": radiisY,
614 "rotationAngles": rotationAngles,
615 "forces": forces,
616 "count": count,
617 "modifiers": modifiers
618 });
619 }
620 },
621
622 _goBack: function() {
623 this._sendAsyncMsg('go-back');
624 },
625
626 _goForward: function() {
627 this._sendAsyncMsg('go-forward');
628 },
629
630 _reload: function(hardReload) {
631 this._sendAsyncMsg('reload', {hardReload: hardReload});
632 },
633
634 _stop: function() {
635 this._sendAsyncMsg('stop');
636 },
637
638 _getScreenshot: function(_width, _height, _mimeType) {
639 let width = parseInt(_width);
640 let height = parseInt(_height);
641 let mimeType = (typeof _mimeType === 'string') ?
642 _mimeType.trim().toLowerCase() : 'image/jpeg';
643 if (isNaN(width) || isNaN(height) || width < 0 || height < 0) {
644 throw Components.Exception("Invalid argument",
645 Cr.NS_ERROR_INVALID_ARG);
646 }
647
648 if (!this._mm) {
649 // Child haven't been loaded.
650 return this._queueDOMRequest('get-screenshot',
651 {width: width, height: height,
652 mimeType: mimeType});
653 }
654
655 return this._sendDOMRequest('get-screenshot',
656 {width: width, height: height,
657 mimeType: mimeType});
658 },
659
660 _recvNextPaint: function(data) {
661 let listeners = this._nextPaintListeners;
662 this._nextPaintListeners = [];
663 for (let listener of listeners) {
664 try {
665 listener();
666 } catch (e) {
667 // If a listener throws we'll continue.
668 }
669 }
670 },
671
672 _addNextPaintListener: function(listener) {
673 if (typeof listener != 'function')
674 throw Components.Exception("Invalid argument", Cr.NS_ERROR_INVALID_ARG);
675
676 let self = this;
677 let run = function() {
678 if (self._nextPaintListeners.push(listener) == 1)
679 self._sendAsyncMsg('activate-next-paint-listener');
680 };
681 if (!this._mm) {
682 this._pendingAPICalls.push(run);
683 } else {
684 run();
685 }
686 },
687
688 _removeNextPaintListener: function(listener) {
689 if (typeof listener != 'function')
690 throw Components.Exception("Invalid argument", Cr.NS_ERROR_INVALID_ARG);
691
692 let self = this;
693 let run = function() {
694 for (let i = self._nextPaintListeners.length - 1; i >= 0; i--) {
695 if (self._nextPaintListeners[i] == listener) {
696 self._nextPaintListeners.splice(i, 1);
697 break;
698 }
699 }
700
701 if (self._nextPaintListeners.length == 0)
702 self._sendAsyncMsg('deactivate-next-paint-listener');
703 };
704 if (!this._mm) {
705 this._pendingAPICalls.push(run);
706 } else {
707 run();
708 }
709 },
710
711 _setInputMethodActive: function(isActive) {
712 if (typeof isActive !== 'boolean') {
713 throw Components.Exception("Invalid argument",
714 Cr.NS_ERROR_INVALID_ARG);
715 }
716
717 let req = Services.DOMRequest.createRequest(this._window);
718
719 // Deactivate the old input method if needed.
720 if (activeInputFrame && isActive) {
721 if (Cu.isDeadWrapper(activeInputFrame)) {
722 // If the activeInputFrame is already a dead object,
723 // we should simply set it to null directly.
724 activeInputFrame = null;
725 this._sendSetInputMethodActiveDOMRequest(req, isActive);
726 } else {
727 let reqOld = XPCNativeWrapper.unwrap(activeInputFrame)
728 .setInputMethodActive(false);
729
730 // We wan't to continue regardless whether this req succeeded
731 reqOld.onsuccess = reqOld.onerror = function() {
732 let setActive = function() {
733 activeInputFrame = null;
734 this._sendSetInputMethodActiveDOMRequest(req, isActive);
735 }.bind(this);
736
737 if (this._ready) {
738 setActive();
739 return;
740 }
741
742 // Wait for the hello event from BrowserElementChild
743 let onReady = function(aMsg) {
744 if (this._isAlive() && (aMsg.data.msg_name === 'hello')) {
745 setActive();
746
747 this._mm.removeMessageListener('browser-element-api:call',
748 onReady);
749 }
750 }.bind(this);
751
752 this._mm.addMessageListener('browser-element-api:call', onReady);
753 }.bind(this);
754 }
755 } else {
756 this._sendSetInputMethodActiveDOMRequest(req, isActive);
757 }
758 return req;
759 },
760
761 _sendSetInputMethodActiveDOMRequest: function(req, isActive) {
762 let id = 'req_' + this._domRequestCounter++;
763 let data = {
764 id : id,
765 args: { isActive: isActive }
766 };
767 if (this._sendAsyncMsg('set-input-method-active', data)) {
768 activeInputFrame = this._frameElement;
769 this._pendingDOMRequests[id] = req;
770 } else {
771 Services.DOMRequest.fireErrorAsync(req, 'fail');
772 }
773 },
774
775 _fireKeyEvent: function(data) {
776 let evt = this._window.document.createEvent("KeyboardEvent");
777 evt.initKeyEvent(data.json.type, true, true, this._window,
778 false, false, false, false, // modifiers
779 data.json.keyCode,
780 data.json.charCode);
781
782 this._frameElement.dispatchEvent(evt);
783 },
784
785 /**
786 * Called when the visibility of the window which owns this iframe changes.
787 */
788 _ownerVisibilityChange: function() {
789 this._sendAsyncMsg('owner-visibility-change',
790 {visible: !this._window.document.hidden});
791 },
792
793 /*
794 * Called when the child notices that its visibility has changed.
795 *
796 * This is sometimes redundant; for example, the child's visibility may
797 * change in response to a setVisible request that we made here! But it's
798 * not always redundant; for example, the child's visibility may change in
799 * response to its parent docshell being hidden.
800 */
801 _childVisibilityChange: function(data) {
802 debug("_childVisibilityChange(" + data.json.visible + ")");
803 this._frameLoader.visible = data.json.visible;
804
805 this._fireEventFromMsg(data);
806 },
807
808 _exitFullscreen: function() {
809 this._windowUtils.exitFullscreen();
810 },
811
812 _remoteFullscreenOriginChange: function(data) {
813 let origin = data.json._payload_;
814 this._windowUtils.remoteFrameFullscreenChanged(this._frameElement, origin);
815 },
816
817 _remoteFrameFullscreenReverted: function(data) {
818 this._windowUtils.remoteFrameFullscreenReverted();
819 },
820
821 _fireFatalError: function() {
822 let evt = this._createEvent('error', {type: 'fatal'},
823 /* cancelable = */ false);
824 this._frameElement.dispatchEvent(evt);
825 },
826
827 observe: function(subject, topic, data) {
828 switch(topic) {
829 case 'oop-frameloader-crashed':
830 if (this._isAlive() && subject == this._frameLoader) {
831 this._fireFatalError();
832 }
833 break;
834 case 'ask-children-to-exit-fullscreen':
835 if (this._isAlive() &&
836 this._frameElement.ownerDocument == subject &&
837 this._hasRemoteFrame) {
838 this._sendAsyncMsg('exit-fullscreen');
839 }
840 break;
841 case 'remote-browser-frame-shown':
842 if (this._frameLoader == subject) {
843 if (!this._mm) {
844 this._setupMessageListener();
845 this._registerAppManifest();
846 this._runPendingAPICall();
847 }
848 Services.obs.removeObserver(this, 'remote-browser-frame-shown');
849 }
850 default:
851 debug('Unknown topic: ' + topic);
852 break;
853 };
854 },
855 };

mercurial