dom/browser-element/BrowserElementChildPreload.js

Wed, 31 Dec 2014 06:09:35 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Wed, 31 Dec 2014 06:09:35 +0100
changeset 0
6474c204b198
permissions
-rw-r--r--

Cloned upstream origin tor-browser at tor-browser-31.3.0esr-4.5-1-build1
revision ID fc1c9ff7c1b2defdbc039f12214767608f46423f for hacking purpose.

michael@0 1 /* This Source Code Form is subject to the terms of the Mozilla Public
michael@0 2 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
michael@0 3 * You can obtain one at http://mozilla.org/MPL/2.0/. */
michael@0 4
michael@0 5 "use strict";
michael@0 6
michael@0 7 dump("######################## BrowserElementChildPreload.js loaded\n");
michael@0 8
michael@0 9 var BrowserElementIsReady = false;
michael@0 10
michael@0 11 let { classes: Cc, interfaces: Ci, results: Cr, utils: Cu } = Components;
michael@0 12 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
michael@0 13 Cu.import("resource://gre/modules/Services.jsm");
michael@0 14 Cu.import("resource://gre/modules/BrowserElementPromptService.jsm");
michael@0 15
michael@0 16 // Event whitelisted for bubbling.
michael@0 17 let whitelistedEvents = [
michael@0 18 Ci.nsIDOMKeyEvent.DOM_VK_ESCAPE, // Back button.
michael@0 19 Ci.nsIDOMKeyEvent.DOM_VK_SLEEP, // Power button.
michael@0 20 Ci.nsIDOMKeyEvent.DOM_VK_CONTEXT_MENU,
michael@0 21 Ci.nsIDOMKeyEvent.DOM_VK_F5, // Search button.
michael@0 22 Ci.nsIDOMKeyEvent.DOM_VK_PAGE_UP, // Volume up.
michael@0 23 Ci.nsIDOMKeyEvent.DOM_VK_PAGE_DOWN // Volume down.
michael@0 24 ];
michael@0 25
michael@0 26 function debug(msg) {
michael@0 27 //dump("BrowserElementChildPreload - " + msg + "\n");
michael@0 28 }
michael@0 29
michael@0 30 function sendAsyncMsg(msg, data) {
michael@0 31 // Ensure that we don't send any messages before BrowserElementChild.js
michael@0 32 // finishes loading.
michael@0 33 if (!BrowserElementIsReady)
michael@0 34 return;
michael@0 35
michael@0 36 if (!data) {
michael@0 37 data = { };
michael@0 38 }
michael@0 39
michael@0 40 data.msg_name = msg;
michael@0 41 sendAsyncMessage('browser-element-api:call', data);
michael@0 42 }
michael@0 43
michael@0 44 function sendSyncMsg(msg, data) {
michael@0 45 // Ensure that we don't send any messages before BrowserElementChild.js
michael@0 46 // finishes loading.
michael@0 47 if (!BrowserElementIsReady)
michael@0 48 return;
michael@0 49
michael@0 50 if (!data) {
michael@0 51 data = { };
michael@0 52 }
michael@0 53
michael@0 54 data.msg_name = msg;
michael@0 55 return sendSyncMessage('browser-element-api:call', data);
michael@0 56 }
michael@0 57
michael@0 58 let CERTIFICATE_ERROR_PAGE_PREF = 'security.alternate_certificate_error_page';
michael@0 59
michael@0 60 let NS_ERROR_MODULE_BASE_OFFSET = 0x45;
michael@0 61 let NS_ERROR_MODULE_SECURITY= 21;
michael@0 62 function NS_ERROR_GET_MODULE(err) {
michael@0 63 return ((((err) >> 16) - NS_ERROR_MODULE_BASE_OFFSET) & 0x1fff);
michael@0 64 }
michael@0 65
michael@0 66 function NS_ERROR_GET_CODE(err) {
michael@0 67 return ((err) & 0xffff);
michael@0 68 }
michael@0 69
michael@0 70 let SEC_ERROR_BASE = Ci.nsINSSErrorsService.NSS_SEC_ERROR_BASE;
michael@0 71 let SEC_ERROR_UNKNOWN_ISSUER = (SEC_ERROR_BASE + 13);
michael@0 72 let SEC_ERROR_CA_CERT_INVALID = (SEC_ERROR_BASE + 36);
michael@0 73 let SEC_ERROR_UNTRUSTED_ISSUER = (SEC_ERROR_BASE + 20);
michael@0 74 let SEC_ERROR_EXPIRED_ISSUER_CERTIFICATE = (SEC_ERROR_BASE + 30);
michael@0 75 let SEC_ERROR_UNTRUSTED_CERT = (SEC_ERROR_BASE + 21);
michael@0 76 let SEC_ERROR_EXPIRED_CERTIFICATE = (SEC_ERROR_BASE + 11);
michael@0 77 let SEC_ERROR_CERT_SIGNATURE_ALGORITHM_DISABLED = (SEC_ERROR_BASE + 176);
michael@0 78
michael@0 79 let SSL_ERROR_BASE = Ci.nsINSSErrorsService.NSS_SSL_ERROR_BASE;
michael@0 80 let SSL_ERROR_BAD_CERT_DOMAIN = (SSL_ERROR_BASE + 12);
michael@0 81
michael@0 82 function getErrorClass(errorCode) {
michael@0 83 let NSPRCode = -1 * NS_ERROR_GET_CODE(errorCode);
michael@0 84
michael@0 85 switch (NSPRCode) {
michael@0 86 case SEC_ERROR_UNKNOWN_ISSUER:
michael@0 87 case SEC_ERROR_UNTRUSTED_ISSUER:
michael@0 88 case SEC_ERROR_EXPIRED_ISSUER_CERTIFICATE:
michael@0 89 case SEC_ERROR_UNTRUSTED_CERT:
michael@0 90 case SSL_ERROR_BAD_CERT_DOMAIN:
michael@0 91 case SEC_ERROR_EXPIRED_CERTIFICATE:
michael@0 92 case SEC_ERROR_CERT_SIGNATURE_ALGORITHM_DISABLED:
michael@0 93 case SEC_ERROR_CA_CERT_INVALID:
michael@0 94 return Ci.nsINSSErrorsService.ERROR_CLASS_BAD_CERT;
michael@0 95 default:
michael@0 96 return Ci.nsINSSErrorsService.ERROR_CLASS_SSL_PROTOCOL;
michael@0 97 }
michael@0 98
michael@0 99 return null;
michael@0 100 }
michael@0 101
michael@0 102 const OBSERVED_EVENTS = [
michael@0 103 'fullscreen-origin-change',
michael@0 104 'ask-parent-to-exit-fullscreen',
michael@0 105 'ask-parent-to-rollback-fullscreen',
michael@0 106 'xpcom-shutdown',
michael@0 107 'activity-done'
michael@0 108 ];
michael@0 109
michael@0 110 /**
michael@0 111 * The BrowserElementChild implements one half of <iframe mozbrowser>.
michael@0 112 * (The other half is, unsurprisingly, BrowserElementParent.)
michael@0 113 *
michael@0 114 * This script is injected into an <iframe mozbrowser> via
michael@0 115 * nsIMessageManager::LoadFrameScript().
michael@0 116 *
michael@0 117 * Our job here is to listen for events within this frame and bubble them up to
michael@0 118 * the parent process.
michael@0 119 */
michael@0 120
michael@0 121 var global = this;
michael@0 122
michael@0 123 function BrowserElementChild() {
michael@0 124 // Maps outer window id --> weak ref to window. Used by modal dialog code.
michael@0 125 this._windowIDDict = {};
michael@0 126
michael@0 127 // _forcedVisible corresponds to the visibility state our owner has set on us
michael@0 128 // (via iframe.setVisible). ownerVisible corresponds to whether the docShell
michael@0 129 // whose window owns this element is visible.
michael@0 130 //
michael@0 131 // Our docShell is visible iff _forcedVisible and _ownerVisible are both
michael@0 132 // true.
michael@0 133 this._forcedVisible = true;
michael@0 134 this._ownerVisible = true;
michael@0 135
michael@0 136 this._nextPaintHandler = null;
michael@0 137
michael@0 138 this._isContentWindowCreated = false;
michael@0 139 this._pendingSetInputMethodActive = [];
michael@0 140
michael@0 141 this._init();
michael@0 142 };
michael@0 143
michael@0 144 BrowserElementChild.prototype = {
michael@0 145
michael@0 146 QueryInterface: XPCOMUtils.generateQI([Ci.nsIObserver,
michael@0 147 Ci.nsISupportsWeakReference]),
michael@0 148
michael@0 149 _init: function() {
michael@0 150 debug("Starting up.");
michael@0 151
michael@0 152 BrowserElementPromptService.mapWindowToBrowserElementChild(content, this);
michael@0 153
michael@0 154 docShell.QueryInterface(Ci.nsIWebProgress)
michael@0 155 .addProgressListener(this._progressListener,
michael@0 156 Ci.nsIWebProgress.NOTIFY_LOCATION |
michael@0 157 Ci.nsIWebProgress.NOTIFY_SECURITY |
michael@0 158 Ci.nsIWebProgress.NOTIFY_STATE_WINDOW);
michael@0 159
michael@0 160 docShell.QueryInterface(Ci.nsIWebNavigation)
michael@0 161 .sessionHistory = Cc["@mozilla.org/browser/shistory;1"]
michael@0 162 .createInstance(Ci.nsISHistory);
michael@0 163
michael@0 164 // This is necessary to get security web progress notifications.
michael@0 165 var securityUI = Cc['@mozilla.org/secure_browser_ui;1']
michael@0 166 .createInstance(Ci.nsISecureBrowserUI);
michael@0 167 securityUI.init(content);
michael@0 168
michael@0 169 // A cache of the menuitem dom objects keyed by the id we generate
michael@0 170 // and pass to the embedder
michael@0 171 this._ctxHandlers = {};
michael@0 172 // Counter of contextmenu events fired
michael@0 173 this._ctxCounter = 0;
michael@0 174
michael@0 175 this._shuttingDown = false;
michael@0 176
michael@0 177 addEventListener('DOMTitleChanged',
michael@0 178 this._titleChangedHandler.bind(this),
michael@0 179 /* useCapture = */ true,
michael@0 180 /* wantsUntrusted = */ false);
michael@0 181
michael@0 182 addEventListener('DOMLinkAdded',
michael@0 183 this._linkAddedHandler.bind(this),
michael@0 184 /* useCapture = */ true,
michael@0 185 /* wantsUntrusted = */ false);
michael@0 186
michael@0 187 addEventListener('DOMMetaAdded',
michael@0 188 this._metaAddedHandler.bind(this),
michael@0 189 /* useCapture = */ true,
michael@0 190 /* wantsUntrusted = */ false);
michael@0 191
michael@0 192 // This listens to unload events from our message manager, but /not/ from
michael@0 193 // the |content| window. That's because the window's unload event doesn't
michael@0 194 // bubble, and we're not using a capturing listener. If we'd used
michael@0 195 // useCapture == true, we /would/ hear unload events from the window, which
michael@0 196 // is not what we want!
michael@0 197 addEventListener('unload',
michael@0 198 this._unloadHandler.bind(this),
michael@0 199 /* useCapture = */ false,
michael@0 200 /* wantsUntrusted = */ false);
michael@0 201
michael@0 202 // Registers a MozAfterPaint handler for the very first paint.
michael@0 203 this._addMozAfterPaintHandler(function () {
michael@0 204 sendAsyncMsg('firstpaint');
michael@0 205 });
michael@0 206
michael@0 207 let self = this;
michael@0 208
michael@0 209 let mmCalls = {
michael@0 210 "purge-history": this._recvPurgeHistory,
michael@0 211 "get-screenshot": this._recvGetScreenshot,
michael@0 212 "set-visible": this._recvSetVisible,
michael@0 213 "get-visible": this._recvVisible,
michael@0 214 "send-mouse-event": this._recvSendMouseEvent,
michael@0 215 "send-touch-event": this._recvSendTouchEvent,
michael@0 216 "get-can-go-back": this._recvCanGoBack,
michael@0 217 "get-can-go-forward": this._recvCanGoForward,
michael@0 218 "go-back": this._recvGoBack,
michael@0 219 "go-forward": this._recvGoForward,
michael@0 220 "reload": this._recvReload,
michael@0 221 "stop": this._recvStop,
michael@0 222 "unblock-modal-prompt": this._recvStopWaiting,
michael@0 223 "fire-ctx-callback": this._recvFireCtxCallback,
michael@0 224 "owner-visibility-change": this._recvOwnerVisibilityChange,
michael@0 225 "exit-fullscreen": this._recvExitFullscreen.bind(this),
michael@0 226 "activate-next-paint-listener": this._activateNextPaintListener.bind(this),
michael@0 227 "set-input-method-active": this._recvSetInputMethodActive.bind(this),
michael@0 228 "deactivate-next-paint-listener": this._deactivateNextPaintListener.bind(this)
michael@0 229 }
michael@0 230
michael@0 231 addMessageListener("browser-element-api:call", function(aMessage) {
michael@0 232 if (aMessage.data.msg_name in mmCalls) {
michael@0 233 return mmCalls[aMessage.data.msg_name].apply(self, arguments);
michael@0 234 }
michael@0 235 });
michael@0 236
michael@0 237 let els = Cc["@mozilla.org/eventlistenerservice;1"]
michael@0 238 .getService(Ci.nsIEventListenerService);
michael@0 239
michael@0 240 // We are using the system group for those events so if something in the
michael@0 241 // content called .stopPropagation() this will still be called.
michael@0 242 els.addSystemEventListener(global, 'keydown',
michael@0 243 this._keyEventHandler.bind(this),
michael@0 244 /* useCapture = */ true);
michael@0 245 els.addSystemEventListener(global, 'keypress',
michael@0 246 this._keyEventHandler.bind(this),
michael@0 247 /* useCapture = */ true);
michael@0 248 els.addSystemEventListener(global, 'keyup',
michael@0 249 this._keyEventHandler.bind(this),
michael@0 250 /* useCapture = */ true);
michael@0 251 els.addSystemEventListener(global, 'DOMWindowClose',
michael@0 252 this._windowCloseHandler.bind(this),
michael@0 253 /* useCapture = */ false);
michael@0 254 els.addSystemEventListener(global, 'DOMWindowCreated',
michael@0 255 this._windowCreatedHandler.bind(this),
michael@0 256 /* useCapture = */ true);
michael@0 257 els.addSystemEventListener(global, 'DOMWindowResize',
michael@0 258 this._windowResizeHandler.bind(this),
michael@0 259 /* useCapture = */ false);
michael@0 260 els.addSystemEventListener(global, 'contextmenu',
michael@0 261 this._contextmenuHandler.bind(this),
michael@0 262 /* useCapture = */ false);
michael@0 263 els.addSystemEventListener(global, 'scroll',
michael@0 264 this._scrollEventHandler.bind(this),
michael@0 265 /* useCapture = */ false);
michael@0 266
michael@0 267 OBSERVED_EVENTS.forEach((aTopic) => {
michael@0 268 Services.obs.addObserver(this, aTopic, false);
michael@0 269 });
michael@0 270 },
michael@0 271
michael@0 272 observe: function(subject, topic, data) {
michael@0 273 // Ignore notifications not about our document. (Note that |content| /can/
michael@0 274 // be null; see bug 874900.)
michael@0 275 if (topic !== 'activity-done' && (!content || subject != content.document))
michael@0 276 return;
michael@0 277 if (topic == 'activity-done' && docShell !== subject)
michael@0 278 return;
michael@0 279 switch (topic) {
michael@0 280 case 'fullscreen-origin-change':
michael@0 281 sendAsyncMsg('fullscreen-origin-change', { _payload_: data });
michael@0 282 break;
michael@0 283 case 'ask-parent-to-exit-fullscreen':
michael@0 284 sendAsyncMsg('exit-fullscreen');
michael@0 285 break;
michael@0 286 case 'ask-parent-to-rollback-fullscreen':
michael@0 287 sendAsyncMsg('rollback-fullscreen');
michael@0 288 break;
michael@0 289 case 'activity-done':
michael@0 290 sendAsyncMsg('activitydone', { success: (data == 'activity-success') });
michael@0 291 break;
michael@0 292 case 'xpcom-shutdown':
michael@0 293 this._shuttingDown = true;
michael@0 294 break;
michael@0 295 }
michael@0 296 },
michael@0 297
michael@0 298 /**
michael@0 299 * Called when our TabChildGlobal starts to die. This is not called when the
michael@0 300 * page inside |content| unloads.
michael@0 301 */
michael@0 302 _unloadHandler: function() {
michael@0 303 this._shuttingDown = true;
michael@0 304 OBSERVED_EVENTS.forEach((aTopic) => {
michael@0 305 Services.obs.removeObserver(this, aTopic);
michael@0 306 });
michael@0 307 },
michael@0 308
michael@0 309 _tryGetInnerWindowID: function(win) {
michael@0 310 let utils = win.QueryInterface(Ci.nsIInterfaceRequestor)
michael@0 311 .getInterface(Ci.nsIDOMWindowUtils);
michael@0 312 try {
michael@0 313 return utils.currentInnerWindowID;
michael@0 314 }
michael@0 315 catch(e) {
michael@0 316 return null;
michael@0 317 }
michael@0 318 },
michael@0 319
michael@0 320 /**
michael@0 321 * Show a modal prompt. Called by BrowserElementPromptService.
michael@0 322 */
michael@0 323 showModalPrompt: function(win, args) {
michael@0 324 let utils = win.QueryInterface(Ci.nsIInterfaceRequestor)
michael@0 325 .getInterface(Ci.nsIDOMWindowUtils);
michael@0 326
michael@0 327 args.windowID = { outer: utils.outerWindowID,
michael@0 328 inner: this._tryGetInnerWindowID(win) };
michael@0 329 sendAsyncMsg('showmodalprompt', args);
michael@0 330
michael@0 331 let returnValue = this._waitForResult(win);
michael@0 332
michael@0 333 Services.obs.notifyObservers(null, 'BEC:ShownModalPrompt', null);
michael@0 334
michael@0 335 if (args.promptType == 'prompt' ||
michael@0 336 args.promptType == 'confirm' ||
michael@0 337 args.promptType == 'custom-prompt') {
michael@0 338 return returnValue;
michael@0 339 }
michael@0 340 },
michael@0 341
michael@0 342 /**
michael@0 343 * Spin in a nested event loop until we receive a unblock-modal-prompt message for
michael@0 344 * this window.
michael@0 345 */
michael@0 346 _waitForResult: function(win) {
michael@0 347 debug("_waitForResult(" + win + ")");
michael@0 348 let utils = win.QueryInterface(Ci.nsIInterfaceRequestor)
michael@0 349 .getInterface(Ci.nsIDOMWindowUtils);
michael@0 350
michael@0 351 let outerWindowID = utils.outerWindowID;
michael@0 352 let innerWindowID = this._tryGetInnerWindowID(win);
michael@0 353 if (innerWindowID === null) {
michael@0 354 // I have no idea what waiting for a result means when there's no inner
michael@0 355 // window, so let's just bail.
michael@0 356 debug("_waitForResult: No inner window. Bailing.");
michael@0 357 return;
michael@0 358 }
michael@0 359
michael@0 360 this._windowIDDict[outerWindowID] = Cu.getWeakReference(win);
michael@0 361
michael@0 362 debug("Entering modal state (outerWindowID=" + outerWindowID + ", " +
michael@0 363 "innerWindowID=" + innerWindowID + ")");
michael@0 364
michael@0 365 utils.enterModalState();
michael@0 366
michael@0 367 // We'll decrement win.modalDepth when we receive a unblock-modal-prompt message
michael@0 368 // for the window.
michael@0 369 if (!win.modalDepth) {
michael@0 370 win.modalDepth = 0;
michael@0 371 }
michael@0 372 win.modalDepth++;
michael@0 373 let origModalDepth = win.modalDepth;
michael@0 374
michael@0 375 let thread = Services.tm.currentThread;
michael@0 376 debug("Nested event loop - begin");
michael@0 377 while (win.modalDepth == origModalDepth && !this._shuttingDown) {
michael@0 378 // Bail out of the loop if the inner window changed; that means the
michael@0 379 // window navigated. Bail out when we're shutting down because otherwise
michael@0 380 // we'll leak our window.
michael@0 381 if (this._tryGetInnerWindowID(win) !== innerWindowID) {
michael@0 382 debug("_waitForResult: Inner window ID changed " +
michael@0 383 "while in nested event loop.");
michael@0 384 break;
michael@0 385 }
michael@0 386
michael@0 387 thread.processNextEvent(/* mayWait = */ true);
michael@0 388 }
michael@0 389 debug("Nested event loop - finish");
michael@0 390
michael@0 391 // If we exited the loop because the inner window changed, then bail on the
michael@0 392 // modal prompt.
michael@0 393 if (innerWindowID !== this._tryGetInnerWindowID(win)) {
michael@0 394 throw Components.Exception("Modal state aborted by navigation",
michael@0 395 Cr.NS_ERROR_NOT_AVAILABLE);
michael@0 396 }
michael@0 397
michael@0 398 let returnValue = win.modalReturnValue;
michael@0 399 delete win.modalReturnValue;
michael@0 400
michael@0 401 if (!this._shuttingDown) {
michael@0 402 utils.leaveModalState();
michael@0 403 }
michael@0 404
michael@0 405 debug("Leaving modal state (outerID=" + outerWindowID + ", " +
michael@0 406 "innerID=" + innerWindowID + ")");
michael@0 407 return returnValue;
michael@0 408 },
michael@0 409
michael@0 410 _recvStopWaiting: function(msg) {
michael@0 411 let outerID = msg.json.windowID.outer;
michael@0 412 let innerID = msg.json.windowID.inner;
michael@0 413 let returnValue = msg.json.returnValue;
michael@0 414 debug("recvStopWaiting(outer=" + outerID + ", inner=" + innerID +
michael@0 415 ", returnValue=" + returnValue + ")");
michael@0 416
michael@0 417 if (!this._windowIDDict[outerID]) {
michael@0 418 debug("recvStopWaiting: No record of outer window ID " + outerID);
michael@0 419 return;
michael@0 420 }
michael@0 421
michael@0 422 let win = this._windowIDDict[outerID].get();
michael@0 423 delete this._windowIDDict[outerID];
michael@0 424
michael@0 425 if (!win) {
michael@0 426 debug("recvStopWaiting, but window is gone\n");
michael@0 427 return;
michael@0 428 }
michael@0 429
michael@0 430 if (innerID !== this._tryGetInnerWindowID(win)) {
michael@0 431 debug("recvStopWaiting, but inner ID has changed\n");
michael@0 432 return;
michael@0 433 }
michael@0 434
michael@0 435 debug("recvStopWaiting " + win);
michael@0 436 win.modalReturnValue = returnValue;
michael@0 437 win.modalDepth--;
michael@0 438 },
michael@0 439
michael@0 440 _recvExitFullscreen: function() {
michael@0 441 var utils = content.document.defaultView
michael@0 442 .QueryInterface(Ci.nsIInterfaceRequestor)
michael@0 443 .getInterface(Ci.nsIDOMWindowUtils);
michael@0 444 utils.exitFullscreen();
michael@0 445 },
michael@0 446
michael@0 447 _titleChangedHandler: function(e) {
michael@0 448 debug("Got titlechanged: (" + e.target.title + ")");
michael@0 449 var win = e.target.defaultView;
michael@0 450
michael@0 451 // Ignore titlechanges which don't come from the top-level
michael@0 452 // <iframe mozbrowser> window.
michael@0 453 if (win == content) {
michael@0 454 sendAsyncMsg('titlechange', { _payload_: e.target.title });
michael@0 455 }
michael@0 456 else {
michael@0 457 debug("Not top level!");
michael@0 458 }
michael@0 459 },
michael@0 460
michael@0 461 _iconChangedHandler: function(e) {
michael@0 462 debug('Got iconchanged: (' + e.target.href + ')');
michael@0 463 let icon = { href: e.target.href };
michael@0 464 if (e.target.getAttribute('sizes')) {
michael@0 465 icon.sizes = e.target.getAttribute('sizes');
michael@0 466 }
michael@0 467
michael@0 468 sendAsyncMsg('iconchange', icon);
michael@0 469 },
michael@0 470
michael@0 471 _openSearchHandler: function(e) {
michael@0 472 debug('Got opensearch: (' + e.target.href + ')');
michael@0 473
michael@0 474 if (e.target.type !== "application/opensearchdescription+xml") {
michael@0 475 return;
michael@0 476 }
michael@0 477
michael@0 478 sendAsyncMsg('opensearch', { title: e.target.title,
michael@0 479 href: e.target.href });
michael@0 480
michael@0 481 },
michael@0 482
michael@0 483 _manifestChangedHandler: function(e) {
michael@0 484 debug('Got manifestchanged: (' + e.target.href + ')');
michael@0 485 let manifest = { href: e.target.href };
michael@0 486 sendAsyncMsg('manifestchange', manifest);
michael@0 487
michael@0 488 },
michael@0 489
michael@0 490 // Processes the "rel" field in <link> tags and forward to specific handlers.
michael@0 491 _linkAddedHandler: function(e) {
michael@0 492 let win = e.target.ownerDocument.defaultView;
michael@0 493 // Ignore links which don't come from the top-level
michael@0 494 // <iframe mozbrowser> window.
michael@0 495 if (win != content) {
michael@0 496 debug('Not top level!');
michael@0 497 return;
michael@0 498 }
michael@0 499
michael@0 500 let handlers = {
michael@0 501 'icon': this._iconChangedHandler,
michael@0 502 'search': this._openSearchHandler,
michael@0 503 'manifest': this._manifestChangedHandler
michael@0 504 };
michael@0 505
michael@0 506 debug('Got linkAdded: (' + e.target.href + ') ' + e.target.rel);
michael@0 507 e.target.rel.split(' ').forEach(function(x) {
michael@0 508 let token = x.toLowerCase();
michael@0 509 if (handlers[token]) {
michael@0 510 handlers[token](e);
michael@0 511 }
michael@0 512 }, this);
michael@0 513 },
michael@0 514
michael@0 515 _metaAddedHandler: function(e) {
michael@0 516 let win = e.target.ownerDocument.defaultView;
michael@0 517 // Ignore metas which don't come from the top-level
michael@0 518 // <iframe mozbrowser> window.
michael@0 519 if (win != content) {
michael@0 520 debug('Not top level!');
michael@0 521 return;
michael@0 522 }
michael@0 523
michael@0 524 if (!e.target.name) {
michael@0 525 return;
michael@0 526 }
michael@0 527
michael@0 528 debug('Got metaAdded: (' + e.target.name + ') ' + e.target.content);
michael@0 529 if (e.target.name == 'application-name') {
michael@0 530 let meta = { name: e.target.name,
michael@0 531 content: e.target.content };
michael@0 532
michael@0 533 let lang;
michael@0 534 let elm;
michael@0 535
michael@0 536 for (elm = e.target;
michael@0 537 !lang && elm && elm.nodeType == e.target.ELEMENT_NODE;
michael@0 538 elm = elm.parentNode) {
michael@0 539 if (elm.hasAttribute('lang')) {
michael@0 540 lang = elm.getAttribute('lang');
michael@0 541 continue;
michael@0 542 }
michael@0 543
michael@0 544 if (elm.hasAttributeNS('http://www.w3.org/XML/1998/namespace', 'lang')) {
michael@0 545 lang = elm.getAttributeNS('http://www.w3.org/XML/1998/namespace', 'lang');
michael@0 546 continue;
michael@0 547 }
michael@0 548 }
michael@0 549
michael@0 550 // No lang has been detected.
michael@0 551 if (!lang && elm.nodeType == e.target.DOCUMENT_NODE) {
michael@0 552 lang = elm.contentLanguage;
michael@0 553 }
michael@0 554
michael@0 555 if (lang) {
michael@0 556 meta.lang = lang;
michael@0 557 }
michael@0 558
michael@0 559 sendAsyncMsg('metachange', meta);
michael@0 560 }
michael@0 561 },
michael@0 562
michael@0 563 _addMozAfterPaintHandler: function(callback) {
michael@0 564 function onMozAfterPaint() {
michael@0 565 let uri = docShell.QueryInterface(Ci.nsIWebNavigation).currentURI;
michael@0 566 if (uri.spec != "about:blank") {
michael@0 567 debug("Got afterpaint event: " + uri.spec);
michael@0 568 removeEventListener('MozAfterPaint', onMozAfterPaint,
michael@0 569 /* useCapture = */ true);
michael@0 570 callback();
michael@0 571 }
michael@0 572 }
michael@0 573
michael@0 574 addEventListener('MozAfterPaint', onMozAfterPaint, /* useCapture = */ true);
michael@0 575 return onMozAfterPaint;
michael@0 576 },
michael@0 577
michael@0 578 _removeMozAfterPaintHandler: function(listener) {
michael@0 579 removeEventListener('MozAfterPaint', listener,
michael@0 580 /* useCapture = */ true);
michael@0 581 },
michael@0 582
michael@0 583 _activateNextPaintListener: function(e) {
michael@0 584 if (!this._nextPaintHandler) {
michael@0 585 this._nextPaintHandler = this._addMozAfterPaintHandler(function () {
michael@0 586 this._nextPaintHandler = null;
michael@0 587 sendAsyncMsg('nextpaint');
michael@0 588 }.bind(this));
michael@0 589 }
michael@0 590 },
michael@0 591
michael@0 592 _deactivateNextPaintListener: function(e) {
michael@0 593 if (this._nextPaintHandler) {
michael@0 594 this._removeMozAfterPaintHandler(this._nextPaintHandler);
michael@0 595 this._nextPaintHandler = null;
michael@0 596 }
michael@0 597 },
michael@0 598
michael@0 599 _windowCloseHandler: function(e) {
michael@0 600 let win = e.target;
michael@0 601 if (win != content || e.defaultPrevented) {
michael@0 602 return;
michael@0 603 }
michael@0 604
michael@0 605 debug("Closing window " + win);
michael@0 606 sendAsyncMsg('close');
michael@0 607
michael@0 608 // Inform the window implementation that we handled this close ourselves.
michael@0 609 e.preventDefault();
michael@0 610 },
michael@0 611
michael@0 612 _windowCreatedHandler: function(e) {
michael@0 613 let targetDocShell = e.target.defaultView
michael@0 614 .QueryInterface(Ci.nsIInterfaceRequestor)
michael@0 615 .getInterface(Ci.nsIWebNavigation);
michael@0 616 if (targetDocShell != docShell) {
michael@0 617 return;
michael@0 618 }
michael@0 619
michael@0 620 let uri = docShell.QueryInterface(Ci.nsIWebNavigation).currentURI;
michael@0 621 debug("Window created: " + uri.spec);
michael@0 622 if (uri.spec != "about:blank") {
michael@0 623 this._addMozAfterPaintHandler(function () {
michael@0 624 sendAsyncMsg('documentfirstpaint');
michael@0 625 });
michael@0 626 this._isContentWindowCreated = true;
michael@0 627 // Handle pending SetInputMethodActive request.
michael@0 628 while (this._pendingSetInputMethodActive.length > 0) {
michael@0 629 this._recvSetInputMethodActive(this._pendingSetInputMethodActive.shift());
michael@0 630 }
michael@0 631 }
michael@0 632 },
michael@0 633
michael@0 634 _windowResizeHandler: function(e) {
michael@0 635 let win = e.target;
michael@0 636 if (win != content || e.defaultPrevented) {
michael@0 637 return;
michael@0 638 }
michael@0 639
michael@0 640 debug("resizing window " + win);
michael@0 641 sendAsyncMsg('resize', { width: e.detail.width, height: e.detail.height });
michael@0 642
michael@0 643 // Inform the window implementation that we handled this resize ourselves.
michael@0 644 e.preventDefault();
michael@0 645 },
michael@0 646
michael@0 647 _contextmenuHandler: function(e) {
michael@0 648 debug("Got contextmenu");
michael@0 649
michael@0 650 if (e.defaultPrevented) {
michael@0 651 return;
michael@0 652 }
michael@0 653
michael@0 654 this._ctxCounter++;
michael@0 655 this._ctxHandlers = {};
michael@0 656
michael@0 657 var elem = e.target;
michael@0 658 var menuData = {systemTargets: [], contextmenu: null};
michael@0 659 var ctxMenuId = null;
michael@0 660
michael@0 661 while (elem && elem.parentNode) {
michael@0 662 var ctxData = this._getSystemCtxMenuData(elem);
michael@0 663 if (ctxData) {
michael@0 664 menuData.systemTargets.push({
michael@0 665 nodeName: elem.nodeName,
michael@0 666 data: ctxData
michael@0 667 });
michael@0 668 }
michael@0 669
michael@0 670 if (!ctxMenuId && 'hasAttribute' in elem && elem.hasAttribute('contextmenu')) {
michael@0 671 ctxMenuId = elem.getAttribute('contextmenu');
michael@0 672 }
michael@0 673 elem = elem.parentNode;
michael@0 674 }
michael@0 675
michael@0 676 if (ctxMenuId) {
michael@0 677 var menu = e.target.ownerDocument.getElementById(ctxMenuId);
michael@0 678 if (menu) {
michael@0 679 menuData.contextmenu = this._buildMenuObj(menu, '');
michael@0 680 }
michael@0 681 }
michael@0 682
michael@0 683 // The value returned by the contextmenu sync call is true iff the embedder
michael@0 684 // called preventDefault() on its contextmenu event.
michael@0 685 //
michael@0 686 // We call preventDefault() on our contextmenu event iff the embedder called
michael@0 687 // preventDefault() on /its/ contextmenu event. This way, if the embedder
michael@0 688 // ignored the contextmenu event, TabChild will fire a click.
michael@0 689 if (sendSyncMsg('contextmenu', menuData)[0]) {
michael@0 690 e.preventDefault();
michael@0 691 } else {
michael@0 692 this._ctxHandlers = {};
michael@0 693 }
michael@0 694 },
michael@0 695
michael@0 696 _getSystemCtxMenuData: function(elem) {
michael@0 697 if ((elem instanceof Ci.nsIDOMHTMLAnchorElement && elem.href) ||
michael@0 698 (elem instanceof Ci.nsIDOMHTMLAreaElement && elem.href)) {
michael@0 699 return {uri: elem.href};
michael@0 700 }
michael@0 701 if (elem instanceof Ci.nsIImageLoadingContent && elem.currentURI) {
michael@0 702 return {uri: elem.currentURI.spec};
michael@0 703 }
michael@0 704 if (elem instanceof Ci.nsIDOMHTMLImageElement) {
michael@0 705 return {uri: elem.src};
michael@0 706 }
michael@0 707 if (elem instanceof Ci.nsIDOMHTMLMediaElement) {
michael@0 708 let hasVideo = !(elem.readyState >= elem.HAVE_METADATA &&
michael@0 709 (elem.videoWidth == 0 || elem.videoHeight == 0));
michael@0 710 return {uri: elem.currentSrc || elem.src, hasVideo: hasVideo};
michael@0 711 }
michael@0 712 return false;
michael@0 713 },
michael@0 714
michael@0 715 _scrollEventHandler: function(e) {
michael@0 716 let win = e.target.defaultView;
michael@0 717 if (win != content) {
michael@0 718 return;
michael@0 719 }
michael@0 720
michael@0 721 debug("scroll event " + win);
michael@0 722 sendAsyncMsg("scroll", { top: win.scrollY, left: win.scrollX });
michael@0 723 },
michael@0 724
michael@0 725 _recvPurgeHistory: function(data) {
michael@0 726 debug("Received purgeHistory message: (" + data.json.id + ")");
michael@0 727
michael@0 728 let history = docShell.QueryInterface(Ci.nsIWebNavigation).sessionHistory;
michael@0 729
michael@0 730 try {
michael@0 731 if (history && history.count) {
michael@0 732 history.PurgeHistory(history.count);
michael@0 733 }
michael@0 734 } catch(e) {}
michael@0 735
michael@0 736 sendAsyncMsg('got-purge-history', { id: data.json.id, successRv: true });
michael@0 737 },
michael@0 738
michael@0 739 _recvGetScreenshot: function(data) {
michael@0 740 debug("Received getScreenshot message: (" + data.json.id + ")");
michael@0 741
michael@0 742 let self = this;
michael@0 743 let maxWidth = data.json.args.width;
michael@0 744 let maxHeight = data.json.args.height;
michael@0 745 let mimeType = data.json.args.mimeType;
michael@0 746 let domRequestID = data.json.id;
michael@0 747
michael@0 748 let takeScreenshotClosure = function() {
michael@0 749 self._takeScreenshot(maxWidth, maxHeight, mimeType, domRequestID);
michael@0 750 };
michael@0 751
michael@0 752 let maxDelayMS = 2000;
michael@0 753 try {
michael@0 754 maxDelayMS = Services.prefs.getIntPref('dom.browserElement.maxScreenshotDelayMS');
michael@0 755 }
michael@0 756 catch(e) {}
michael@0 757
michael@0 758 // Try to wait for the event loop to go idle before we take the screenshot,
michael@0 759 // but once we've waited maxDelayMS milliseconds, go ahead and take it
michael@0 760 // anyway.
michael@0 761 Cc['@mozilla.org/message-loop;1'].getService(Ci.nsIMessageLoop).postIdleTask(
michael@0 762 takeScreenshotClosure, maxDelayMS);
michael@0 763 },
michael@0 764
michael@0 765 /**
michael@0 766 * Actually take a screenshot and foward the result up to our parent, given
michael@0 767 * the desired maxWidth and maxHeight (in CSS pixels), and given the
michael@0 768 * DOMRequest ID associated with the request from the parent.
michael@0 769 */
michael@0 770 _takeScreenshot: function(maxWidth, maxHeight, mimeType, domRequestID) {
michael@0 771 // You can think of the screenshotting algorithm as carrying out the
michael@0 772 // following steps:
michael@0 773 //
michael@0 774 // - Calculate maxWidth, maxHeight, and viewport's width and height in the
michael@0 775 // dimension of device pixels by multiply the numbers with
michael@0 776 // window.devicePixelRatio.
michael@0 777 //
michael@0 778 // - Let scaleWidth be the factor by which we'd need to downscale the
michael@0 779 // viewport pixel width so it would fit within maxPixelWidth.
michael@0 780 // (If the viewport's pixel width is less than maxPixelWidth, let
michael@0 781 // scaleWidth be 1.) Compute scaleHeight the same way.
michael@0 782 //
michael@0 783 // - Scale the viewport by max(scaleWidth, scaleHeight). Now either the
michael@0 784 // viewport's width is no larger than maxWidth, the viewport's height is
michael@0 785 // no larger than maxHeight, or both.
michael@0 786 //
michael@0 787 // - Crop the viewport so its width is no larger than maxWidth and its
michael@0 788 // height is no larger than maxHeight.
michael@0 789 //
michael@0 790 // - Set mozOpaque to true and background color to solid white
michael@0 791 // if we are taking a JPEG screenshot, keep transparent if otherwise.
michael@0 792 //
michael@0 793 // - Return a screenshot of the page's viewport scaled and cropped per
michael@0 794 // above.
michael@0 795 debug("Taking a screenshot: maxWidth=" + maxWidth +
michael@0 796 ", maxHeight=" + maxHeight +
michael@0 797 ", mimeType=" + mimeType +
michael@0 798 ", domRequestID=" + domRequestID + ".");
michael@0 799
michael@0 800 if (!content) {
michael@0 801 // If content is not loaded yet, bail out since even sendAsyncMessage
michael@0 802 // fails...
michael@0 803 debug("No content yet!");
michael@0 804 return;
michael@0 805 }
michael@0 806
michael@0 807 let devicePixelRatio = content.devicePixelRatio;
michael@0 808
michael@0 809 let maxPixelWidth = Math.round(maxWidth * devicePixelRatio);
michael@0 810 let maxPixelHeight = Math.round(maxHeight * devicePixelRatio);
michael@0 811
michael@0 812 let contentPixelWidth = content.innerWidth * devicePixelRatio;
michael@0 813 let contentPixelHeight = content.innerHeight * devicePixelRatio;
michael@0 814
michael@0 815 let scaleWidth = Math.min(1, maxPixelWidth / contentPixelWidth);
michael@0 816 let scaleHeight = Math.min(1, maxPixelHeight / contentPixelHeight);
michael@0 817
michael@0 818 let scale = Math.max(scaleWidth, scaleHeight);
michael@0 819
michael@0 820 let canvasWidth =
michael@0 821 Math.min(maxPixelWidth, Math.round(contentPixelWidth * scale));
michael@0 822 let canvasHeight =
michael@0 823 Math.min(maxPixelHeight, Math.round(contentPixelHeight * scale));
michael@0 824
michael@0 825 let transparent = (mimeType !== 'image/jpeg');
michael@0 826
michael@0 827 var canvas = content.document
michael@0 828 .createElementNS("http://www.w3.org/1999/xhtml", "canvas");
michael@0 829 if (!transparent)
michael@0 830 canvas.mozOpaque = true;
michael@0 831 canvas.width = canvasWidth;
michael@0 832 canvas.height = canvasHeight;
michael@0 833
michael@0 834 let ctx = canvas.getContext("2d", { willReadFrequently: true });
michael@0 835 ctx.scale(scale * devicePixelRatio, scale * devicePixelRatio);
michael@0 836
michael@0 837 let flags = ctx.DRAWWINDOW_DRAW_VIEW |
michael@0 838 ctx.DRAWWINDOW_USE_WIDGET_LAYERS |
michael@0 839 ctx.DRAWWINDOW_DO_NOT_FLUSH |
michael@0 840 ctx.DRAWWINDOW_ASYNC_DECODE_IMAGES;
michael@0 841 ctx.drawWindow(content, 0, 0, content.innerWidth, content.innerHeight,
michael@0 842 transparent ? "rgba(255,255,255,0)" : "rgb(255,255,255)",
michael@0 843 flags);
michael@0 844
michael@0 845 // Take a JPEG screenshot by default instead of PNG with alpha channel.
michael@0 846 // This requires us to unpremultiply the alpha channel, which
michael@0 847 // is expensive on ARM processors because they lack a hardware integer
michael@0 848 // division instruction.
michael@0 849 canvas.toBlob(function(blob) {
michael@0 850 sendAsyncMsg('got-screenshot', {
michael@0 851 id: domRequestID,
michael@0 852 successRv: blob
michael@0 853 });
michael@0 854 }, mimeType);
michael@0 855 },
michael@0 856
michael@0 857 _recvFireCtxCallback: function(data) {
michael@0 858 debug("Received fireCtxCallback message: (" + data.json.menuitem + ")");
michael@0 859 // We silently ignore if the embedder uses an incorrect id in the callback
michael@0 860 if (data.json.menuitem in this._ctxHandlers) {
michael@0 861 this._ctxHandlers[data.json.menuitem].click();
michael@0 862 this._ctxHandlers = {};
michael@0 863 } else {
michael@0 864 debug("Ignored invalid contextmenu invocation");
michael@0 865 }
michael@0 866 },
michael@0 867
michael@0 868 _buildMenuObj: function(menu, idPrefix) {
michael@0 869 function maybeCopyAttribute(src, target, attribute) {
michael@0 870 if (src.getAttribute(attribute)) {
michael@0 871 target[attribute] = src.getAttribute(attribute);
michael@0 872 }
michael@0 873 }
michael@0 874
michael@0 875 var menuObj = {type: 'menu', items: []};
michael@0 876 maybeCopyAttribute(menu, menuObj, 'label');
michael@0 877
michael@0 878 for (var i = 0, child; child = menu.children[i++];) {
michael@0 879 if (child.nodeName === 'MENU') {
michael@0 880 menuObj.items.push(this._buildMenuObj(child, idPrefix + i + '_'));
michael@0 881 } else if (child.nodeName === 'MENUITEM') {
michael@0 882 var id = this._ctxCounter + '_' + idPrefix + i;
michael@0 883 var menuitem = {id: id, type: 'menuitem'};
michael@0 884 maybeCopyAttribute(child, menuitem, 'label');
michael@0 885 maybeCopyAttribute(child, menuitem, 'icon');
michael@0 886 this._ctxHandlers[id] = child;
michael@0 887 menuObj.items.push(menuitem);
michael@0 888 }
michael@0 889 }
michael@0 890 return menuObj;
michael@0 891 },
michael@0 892
michael@0 893 _recvSetVisible: function(data) {
michael@0 894 debug("Received setVisible message: (" + data.json.visible + ")");
michael@0 895 if (this._forcedVisible == data.json.visible) {
michael@0 896 return;
michael@0 897 }
michael@0 898
michael@0 899 this._forcedVisible = data.json.visible;
michael@0 900 this._updateVisibility();
michael@0 901 },
michael@0 902
michael@0 903 _recvVisible: function(data) {
michael@0 904 sendAsyncMsg('got-visible', {
michael@0 905 id: data.json.id,
michael@0 906 successRv: docShell.isActive
michael@0 907 });
michael@0 908 },
michael@0 909
michael@0 910 /**
michael@0 911 * Called when the window which contains this iframe becomes hidden or
michael@0 912 * visible.
michael@0 913 */
michael@0 914 _recvOwnerVisibilityChange: function(data) {
michael@0 915 debug("Received ownerVisibilityChange: (" + data.json.visible + ")");
michael@0 916 this._ownerVisible = data.json.visible;
michael@0 917 this._updateVisibility();
michael@0 918 },
michael@0 919
michael@0 920 _updateVisibility: function() {
michael@0 921 var visible = this._forcedVisible && this._ownerVisible;
michael@0 922 if (docShell.isActive !== visible) {
michael@0 923 docShell.isActive = visible;
michael@0 924 sendAsyncMsg('visibilitychange', {visible: visible});
michael@0 925 }
michael@0 926 },
michael@0 927
michael@0 928 _recvSendMouseEvent: function(data) {
michael@0 929 let json = data.json;
michael@0 930 let utils = content.QueryInterface(Ci.nsIInterfaceRequestor)
michael@0 931 .getInterface(Ci.nsIDOMWindowUtils);
michael@0 932 utils.sendMouseEventToWindow(json.type, json.x, json.y, json.button,
michael@0 933 json.clickCount, json.modifiers);
michael@0 934 },
michael@0 935
michael@0 936 _recvSendTouchEvent: function(data) {
michael@0 937 let json = data.json;
michael@0 938 let utils = content.QueryInterface(Ci.nsIInterfaceRequestor)
michael@0 939 .getInterface(Ci.nsIDOMWindowUtils);
michael@0 940 utils.sendTouchEventToWindow(json.type, json.identifiers, json.touchesX,
michael@0 941 json.touchesY, json.radiisX, json.radiisY,
michael@0 942 json.rotationAngles, json.forces, json.count,
michael@0 943 json.modifiers);
michael@0 944 },
michael@0 945
michael@0 946 _recvCanGoBack: function(data) {
michael@0 947 var webNav = docShell.QueryInterface(Ci.nsIWebNavigation);
michael@0 948 sendAsyncMsg('got-can-go-back', {
michael@0 949 id: data.json.id,
michael@0 950 successRv: webNav.canGoBack
michael@0 951 });
michael@0 952 },
michael@0 953
michael@0 954 _recvCanGoForward: function(data) {
michael@0 955 var webNav = docShell.QueryInterface(Ci.nsIWebNavigation);
michael@0 956 sendAsyncMsg('got-can-go-forward', {
michael@0 957 id: data.json.id,
michael@0 958 successRv: webNav.canGoForward
michael@0 959 });
michael@0 960 },
michael@0 961
michael@0 962 _recvGoBack: function(data) {
michael@0 963 try {
michael@0 964 docShell.QueryInterface(Ci.nsIWebNavigation).goBack();
michael@0 965 } catch(e) {
michael@0 966 // Silently swallow errors; these happen when we can't go back.
michael@0 967 }
michael@0 968 },
michael@0 969
michael@0 970 _recvGoForward: function(data) {
michael@0 971 try {
michael@0 972 docShell.QueryInterface(Ci.nsIWebNavigation).goForward();
michael@0 973 } catch(e) {
michael@0 974 // Silently swallow errors; these happen when we can't go forward.
michael@0 975 }
michael@0 976 },
michael@0 977
michael@0 978 _recvReload: function(data) {
michael@0 979 let webNav = docShell.QueryInterface(Ci.nsIWebNavigation);
michael@0 980 let reloadFlags = data.json.hardReload ?
michael@0 981 webNav.LOAD_FLAGS_BYPASS_PROXY | webNav.LOAD_FLAGS_BYPASS_CACHE :
michael@0 982 webNav.LOAD_FLAGS_NONE;
michael@0 983 try {
michael@0 984 webNav.reload(reloadFlags);
michael@0 985 } catch(e) {
michael@0 986 // Silently swallow errors; these can happen if a used cancels reload
michael@0 987 }
michael@0 988 },
michael@0 989
michael@0 990 _recvStop: function(data) {
michael@0 991 let webNav = docShell.QueryInterface(Ci.nsIWebNavigation);
michael@0 992 webNav.stop(webNav.STOP_NETWORK);
michael@0 993 },
michael@0 994
michael@0 995 _recvSetInputMethodActive: function(data) {
michael@0 996 let msgData = { id: data.json.id };
michael@0 997 if (!this._isContentWindowCreated) {
michael@0 998 if (data.json.args.isActive) {
michael@0 999 // To activate the input method, we should wait before the content
michael@0 1000 // window is ready.
michael@0 1001 this._pendingSetInputMethodActive.push(data);
michael@0 1002 return;
michael@0 1003 }
michael@0 1004 msgData.successRv = null;
michael@0 1005 sendAsyncMsg('got-set-input-method-active', msgData);
michael@0 1006 return;
michael@0 1007 }
michael@0 1008 // Unwrap to access webpage content.
michael@0 1009 let nav = XPCNativeWrapper.unwrap(content.document.defaultView.navigator);
michael@0 1010 if (nav.mozInputMethod) {
michael@0 1011 // Wrap to access the chrome-only attribute setActive.
michael@0 1012 new XPCNativeWrapper(nav.mozInputMethod).setActive(data.json.args.isActive);
michael@0 1013 msgData.successRv = null;
michael@0 1014 } else {
michael@0 1015 msgData.errorMsg = 'Cannot access mozInputMethod.';
michael@0 1016 }
michael@0 1017 sendAsyncMsg('got-set-input-method-active', msgData);
michael@0 1018 },
michael@0 1019
michael@0 1020 _keyEventHandler: function(e) {
michael@0 1021 if (whitelistedEvents.indexOf(e.keyCode) != -1 && !e.defaultPrevented) {
michael@0 1022 sendAsyncMsg('keyevent', {
michael@0 1023 type: e.type,
michael@0 1024 keyCode: e.keyCode,
michael@0 1025 charCode: e.charCode,
michael@0 1026 });
michael@0 1027 }
michael@0 1028 },
michael@0 1029
michael@0 1030 // The docShell keeps a weak reference to the progress listener, so we need
michael@0 1031 // to keep a strong ref to it ourselves.
michael@0 1032 _progressListener: {
michael@0 1033 QueryInterface: XPCOMUtils.generateQI([Ci.nsIWebProgressListener,
michael@0 1034 Ci.nsISupportsWeakReference]),
michael@0 1035 _seenLoadStart: false,
michael@0 1036
michael@0 1037 onLocationChange: function(webProgress, request, location, flags) {
michael@0 1038 // We get progress events from subshells here, which is kind of weird.
michael@0 1039 if (webProgress != docShell) {
michael@0 1040 return;
michael@0 1041 }
michael@0 1042
michael@0 1043 // Ignore locationchange events which occur before the first loadstart.
michael@0 1044 // These are usually about:blank loads we don't care about.
michael@0 1045 if (!this._seenLoadStart) {
michael@0 1046 return;
michael@0 1047 }
michael@0 1048
michael@0 1049 // Remove password and wyciwyg from uri.
michael@0 1050 location = Cc["@mozilla.org/docshell/urifixup;1"]
michael@0 1051 .getService(Ci.nsIURIFixup).createExposableURI(location);
michael@0 1052
michael@0 1053 sendAsyncMsg('locationchange', { _payload_: location.spec });
michael@0 1054 },
michael@0 1055
michael@0 1056 onStateChange: function(webProgress, request, stateFlags, status) {
michael@0 1057 if (webProgress != docShell) {
michael@0 1058 return;
michael@0 1059 }
michael@0 1060
michael@0 1061 if (stateFlags & Ci.nsIWebProgressListener.STATE_START) {
michael@0 1062 this._seenLoadStart = true;
michael@0 1063 sendAsyncMsg('loadstart');
michael@0 1064 }
michael@0 1065
michael@0 1066 if (stateFlags & Ci.nsIWebProgressListener.STATE_STOP) {
michael@0 1067 let bgColor = 'transparent';
michael@0 1068 try {
michael@0 1069 bgColor = content.getComputedStyle(content.document.body)
michael@0 1070 .getPropertyValue('background-color');
michael@0 1071 } catch (e) {}
michael@0 1072 sendAsyncMsg('loadend', {backgroundColor: bgColor});
michael@0 1073
michael@0 1074 // Ignoring NS_BINDING_ABORTED, which is set when loading page is
michael@0 1075 // stopped.
michael@0 1076 if (status == Cr.NS_OK ||
michael@0 1077 status == Cr.NS_BINDING_ABORTED) {
michael@0 1078 return;
michael@0 1079 }
michael@0 1080
michael@0 1081 if (NS_ERROR_GET_MODULE(status) == NS_ERROR_MODULE_SECURITY &&
michael@0 1082 getErrorClass(status) == Ci.nsINSSErrorsService.ERROR_CLASS_BAD_CERT) {
michael@0 1083
michael@0 1084 // XXX Is there a point firing the event if the error page is not
michael@0 1085 // certerror? If yes, maybe we should add a property to the
michael@0 1086 // event to to indicate whether there is a custom page. That would
michael@0 1087 // let the embedder have more control over the desired behavior.
michael@0 1088 var errorPage = null;
michael@0 1089 try {
michael@0 1090 errorPage = Services.prefs.getCharPref(CERTIFICATE_ERROR_PAGE_PREF);
michael@0 1091 } catch(e) {}
michael@0 1092
michael@0 1093 if (errorPage == 'certerror') {
michael@0 1094 sendAsyncMsg('error', { type: 'certerror' });
michael@0 1095 return;
michael@0 1096 }
michael@0 1097 }
michael@0 1098
michael@0 1099 // TODO See nsDocShell::DisplayLoadError for a list of all the error
michael@0 1100 // codes (the status param) we should eventually handle here.
michael@0 1101 sendAsyncMsg('error', { type: 'other' });
michael@0 1102 }
michael@0 1103 },
michael@0 1104
michael@0 1105 onSecurityChange: function(webProgress, request, state) {
michael@0 1106 if (webProgress != docShell) {
michael@0 1107 return;
michael@0 1108 }
michael@0 1109
michael@0 1110 var stateDesc;
michael@0 1111 if (state & Ci.nsIWebProgressListener.STATE_IS_SECURE) {
michael@0 1112 stateDesc = 'secure';
michael@0 1113 }
michael@0 1114 else if (state & Ci.nsIWebProgressListener.STATE_IS_BROKEN) {
michael@0 1115 stateDesc = 'broken';
michael@0 1116 }
michael@0 1117 else if (state & Ci.nsIWebProgressListener.STATE_IS_INSECURE) {
michael@0 1118 stateDesc = 'insecure';
michael@0 1119 }
michael@0 1120 else {
michael@0 1121 debug("Unexpected securitychange state!");
michael@0 1122 stateDesc = '???';
michael@0 1123 }
michael@0 1124
michael@0 1125 // XXX Until bug 764496 is fixed, this will always return false.
michael@0 1126 var isEV = !!(state & Ci.nsIWebProgressListener.STATE_IDENTITY_EV_TOPLEVEL);
michael@0 1127
michael@0 1128 sendAsyncMsg('securitychange', { state: stateDesc, extendedValidation: isEV });
michael@0 1129 },
michael@0 1130
michael@0 1131 onStatusChange: function(webProgress, request, status, message) {},
michael@0 1132 onProgressChange: function(webProgress, request, curSelfProgress,
michael@0 1133 maxSelfProgress, curTotalProgress, maxTotalProgress) {},
michael@0 1134 },
michael@0 1135
michael@0 1136 // Expose the message manager for WebApps and others.
michael@0 1137 _messageManagerPublic: {
michael@0 1138 sendAsyncMessage: global.sendAsyncMessage.bind(global),
michael@0 1139 sendSyncMessage: global.sendSyncMessage.bind(global),
michael@0 1140 addMessageListener: global.addMessageListener.bind(global),
michael@0 1141 removeMessageListener: global.removeMessageListener.bind(global)
michael@0 1142 },
michael@0 1143
michael@0 1144 get messageManager() {
michael@0 1145 return this._messageManagerPublic;
michael@0 1146 }
michael@0 1147 };
michael@0 1148
michael@0 1149 var api = new BrowserElementChild();
michael@0 1150

mercurial