dom/browser-element/BrowserElementChildPreload.js

changeset 0
6474c204b198
     1.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     1.2 +++ b/dom/browser-element/BrowserElementChildPreload.js	Wed Dec 31 06:09:35 2014 +0100
     1.3 @@ -0,0 +1,1150 @@
     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 +dump("######################## BrowserElementChildPreload.js loaded\n");
    1.11 +
    1.12 +var BrowserElementIsReady = false;
    1.13 +
    1.14 +let { classes: Cc, interfaces: Ci, results: Cr, utils: Cu }  = Components;
    1.15 +Cu.import("resource://gre/modules/XPCOMUtils.jsm");
    1.16 +Cu.import("resource://gre/modules/Services.jsm");
    1.17 +Cu.import("resource://gre/modules/BrowserElementPromptService.jsm");
    1.18 +
    1.19 +// Event whitelisted for bubbling.
    1.20 +let whitelistedEvents = [
    1.21 +  Ci.nsIDOMKeyEvent.DOM_VK_ESCAPE,   // Back button.
    1.22 +  Ci.nsIDOMKeyEvent.DOM_VK_SLEEP,    // Power button.
    1.23 +  Ci.nsIDOMKeyEvent.DOM_VK_CONTEXT_MENU,
    1.24 +  Ci.nsIDOMKeyEvent.DOM_VK_F5,       // Search button.
    1.25 +  Ci.nsIDOMKeyEvent.DOM_VK_PAGE_UP,  // Volume up.
    1.26 +  Ci.nsIDOMKeyEvent.DOM_VK_PAGE_DOWN // Volume down.
    1.27 +];
    1.28 +
    1.29 +function debug(msg) {
    1.30 +  //dump("BrowserElementChildPreload - " + msg + "\n");
    1.31 +}
    1.32 +
    1.33 +function sendAsyncMsg(msg, data) {
    1.34 +  // Ensure that we don't send any messages before BrowserElementChild.js
    1.35 +  // finishes loading.
    1.36 +  if (!BrowserElementIsReady)
    1.37 +    return;
    1.38 +
    1.39 +  if (!data) {
    1.40 +    data = { };
    1.41 +  }
    1.42 +
    1.43 +  data.msg_name = msg;
    1.44 +  sendAsyncMessage('browser-element-api:call', data);
    1.45 +}
    1.46 +
    1.47 +function sendSyncMsg(msg, data) {
    1.48 +  // Ensure that we don't send any messages before BrowserElementChild.js
    1.49 +  // finishes loading.
    1.50 +  if (!BrowserElementIsReady)
    1.51 +    return;
    1.52 +
    1.53 +  if (!data) {
    1.54 +    data = { };
    1.55 +  }
    1.56 +
    1.57 +  data.msg_name = msg;
    1.58 +  return sendSyncMessage('browser-element-api:call', data);
    1.59 +}
    1.60 +
    1.61 +let CERTIFICATE_ERROR_PAGE_PREF = 'security.alternate_certificate_error_page';
    1.62 +
    1.63 +let NS_ERROR_MODULE_BASE_OFFSET = 0x45;
    1.64 +let NS_ERROR_MODULE_SECURITY= 21;
    1.65 +function NS_ERROR_GET_MODULE(err) {
    1.66 +  return ((((err) >> 16) - NS_ERROR_MODULE_BASE_OFFSET) & 0x1fff);
    1.67 +}
    1.68 +
    1.69 +function NS_ERROR_GET_CODE(err) {
    1.70 +  return ((err) & 0xffff);
    1.71 +}
    1.72 +
    1.73 +let SEC_ERROR_BASE = Ci.nsINSSErrorsService.NSS_SEC_ERROR_BASE;
    1.74 +let SEC_ERROR_UNKNOWN_ISSUER = (SEC_ERROR_BASE + 13);
    1.75 +let SEC_ERROR_CA_CERT_INVALID =   (SEC_ERROR_BASE + 36);
    1.76 +let SEC_ERROR_UNTRUSTED_ISSUER = (SEC_ERROR_BASE + 20);
    1.77 +let SEC_ERROR_EXPIRED_ISSUER_CERTIFICATE = (SEC_ERROR_BASE + 30);
    1.78 +let SEC_ERROR_UNTRUSTED_CERT = (SEC_ERROR_BASE + 21);
    1.79 +let SEC_ERROR_EXPIRED_CERTIFICATE = (SEC_ERROR_BASE + 11);
    1.80 +let SEC_ERROR_CERT_SIGNATURE_ALGORITHM_DISABLED = (SEC_ERROR_BASE + 176);
    1.81 +
    1.82 +let SSL_ERROR_BASE = Ci.nsINSSErrorsService.NSS_SSL_ERROR_BASE;
    1.83 +let SSL_ERROR_BAD_CERT_DOMAIN = (SSL_ERROR_BASE + 12);
    1.84 +
    1.85 +function getErrorClass(errorCode) {
    1.86 +  let NSPRCode = -1 * NS_ERROR_GET_CODE(errorCode);
    1.87 +
    1.88 +  switch (NSPRCode) {
    1.89 +    case SEC_ERROR_UNKNOWN_ISSUER:
    1.90 +    case SEC_ERROR_UNTRUSTED_ISSUER:
    1.91 +    case SEC_ERROR_EXPIRED_ISSUER_CERTIFICATE:
    1.92 +    case SEC_ERROR_UNTRUSTED_CERT:
    1.93 +    case SSL_ERROR_BAD_CERT_DOMAIN:
    1.94 +    case SEC_ERROR_EXPIRED_CERTIFICATE:
    1.95 +    case SEC_ERROR_CERT_SIGNATURE_ALGORITHM_DISABLED:
    1.96 +    case SEC_ERROR_CA_CERT_INVALID:
    1.97 +      return Ci.nsINSSErrorsService.ERROR_CLASS_BAD_CERT;
    1.98 +    default:
    1.99 +      return Ci.nsINSSErrorsService.ERROR_CLASS_SSL_PROTOCOL;
   1.100 +  }
   1.101 +
   1.102 +  return null;
   1.103 +}
   1.104 +
   1.105 +const OBSERVED_EVENTS = [
   1.106 +  'fullscreen-origin-change',
   1.107 +  'ask-parent-to-exit-fullscreen',
   1.108 +  'ask-parent-to-rollback-fullscreen',
   1.109 +  'xpcom-shutdown',
   1.110 +  'activity-done'
   1.111 +];
   1.112 +
   1.113 +/**
   1.114 + * The BrowserElementChild implements one half of <iframe mozbrowser>.
   1.115 + * (The other half is, unsurprisingly, BrowserElementParent.)
   1.116 + *
   1.117 + * This script is injected into an <iframe mozbrowser> via
   1.118 + * nsIMessageManager::LoadFrameScript().
   1.119 + *
   1.120 + * Our job here is to listen for events within this frame and bubble them up to
   1.121 + * the parent process.
   1.122 + */
   1.123 +
   1.124 +var global = this;
   1.125 +
   1.126 +function BrowserElementChild() {
   1.127 +  // Maps outer window id --> weak ref to window.  Used by modal dialog code.
   1.128 +  this._windowIDDict = {};
   1.129 +
   1.130 +  // _forcedVisible corresponds to the visibility state our owner has set on us
   1.131 +  // (via iframe.setVisible).  ownerVisible corresponds to whether the docShell
   1.132 +  // whose window owns this element is visible.
   1.133 +  //
   1.134 +  // Our docShell is visible iff _forcedVisible and _ownerVisible are both
   1.135 +  // true.
   1.136 +  this._forcedVisible = true;
   1.137 +  this._ownerVisible = true;
   1.138 +
   1.139 +  this._nextPaintHandler = null;
   1.140 +
   1.141 +  this._isContentWindowCreated = false;
   1.142 +  this._pendingSetInputMethodActive = [];
   1.143 +
   1.144 +  this._init();
   1.145 +};
   1.146 +
   1.147 +BrowserElementChild.prototype = {
   1.148 +
   1.149 +  QueryInterface: XPCOMUtils.generateQI([Ci.nsIObserver,
   1.150 +                                         Ci.nsISupportsWeakReference]),
   1.151 +
   1.152 +  _init: function() {
   1.153 +    debug("Starting up.");
   1.154 +
   1.155 +    BrowserElementPromptService.mapWindowToBrowserElementChild(content, this);
   1.156 +
   1.157 +    docShell.QueryInterface(Ci.nsIWebProgress)
   1.158 +            .addProgressListener(this._progressListener,
   1.159 +                                 Ci.nsIWebProgress.NOTIFY_LOCATION |
   1.160 +                                 Ci.nsIWebProgress.NOTIFY_SECURITY |
   1.161 +                                 Ci.nsIWebProgress.NOTIFY_STATE_WINDOW);
   1.162 +
   1.163 +    docShell.QueryInterface(Ci.nsIWebNavigation)
   1.164 +            .sessionHistory = Cc["@mozilla.org/browser/shistory;1"]
   1.165 +                                .createInstance(Ci.nsISHistory);
   1.166 +
   1.167 +    // This is necessary to get security web progress notifications.
   1.168 +    var securityUI = Cc['@mozilla.org/secure_browser_ui;1']
   1.169 +                       .createInstance(Ci.nsISecureBrowserUI);
   1.170 +    securityUI.init(content);
   1.171 +
   1.172 +    // A cache of the menuitem dom objects keyed by the id we generate
   1.173 +    // and pass to the embedder
   1.174 +    this._ctxHandlers = {};
   1.175 +    // Counter of contextmenu events fired
   1.176 +    this._ctxCounter = 0;
   1.177 +
   1.178 +    this._shuttingDown = false;
   1.179 +
   1.180 +    addEventListener('DOMTitleChanged',
   1.181 +                     this._titleChangedHandler.bind(this),
   1.182 +                     /* useCapture = */ true,
   1.183 +                     /* wantsUntrusted = */ false);
   1.184 +
   1.185 +    addEventListener('DOMLinkAdded',
   1.186 +                     this._linkAddedHandler.bind(this),
   1.187 +                     /* useCapture = */ true,
   1.188 +                     /* wantsUntrusted = */ false);
   1.189 +
   1.190 +    addEventListener('DOMMetaAdded',
   1.191 +                     this._metaAddedHandler.bind(this),
   1.192 +                     /* useCapture = */ true,
   1.193 +                     /* wantsUntrusted = */ false);
   1.194 +
   1.195 +    // This listens to unload events from our message manager, but /not/ from
   1.196 +    // the |content| window.  That's because the window's unload event doesn't
   1.197 +    // bubble, and we're not using a capturing listener.  If we'd used
   1.198 +    // useCapture == true, we /would/ hear unload events from the window, which
   1.199 +    // is not what we want!
   1.200 +    addEventListener('unload',
   1.201 +                     this._unloadHandler.bind(this),
   1.202 +                     /* useCapture = */ false,
   1.203 +                     /* wantsUntrusted = */ false);
   1.204 +
   1.205 +    // Registers a MozAfterPaint handler for the very first paint.
   1.206 +    this._addMozAfterPaintHandler(function () {
   1.207 +      sendAsyncMsg('firstpaint');
   1.208 +    });
   1.209 +
   1.210 +    let self = this;
   1.211 +
   1.212 +    let mmCalls = {
   1.213 +      "purge-history": this._recvPurgeHistory,
   1.214 +      "get-screenshot": this._recvGetScreenshot,
   1.215 +      "set-visible": this._recvSetVisible,
   1.216 +      "get-visible": this._recvVisible,
   1.217 +      "send-mouse-event": this._recvSendMouseEvent,
   1.218 +      "send-touch-event": this._recvSendTouchEvent,
   1.219 +      "get-can-go-back": this._recvCanGoBack,
   1.220 +      "get-can-go-forward": this._recvCanGoForward,
   1.221 +      "go-back": this._recvGoBack,
   1.222 +      "go-forward": this._recvGoForward,
   1.223 +      "reload": this._recvReload,
   1.224 +      "stop": this._recvStop,
   1.225 +      "unblock-modal-prompt": this._recvStopWaiting,
   1.226 +      "fire-ctx-callback": this._recvFireCtxCallback,
   1.227 +      "owner-visibility-change": this._recvOwnerVisibilityChange,
   1.228 +      "exit-fullscreen": this._recvExitFullscreen.bind(this),
   1.229 +      "activate-next-paint-listener": this._activateNextPaintListener.bind(this),
   1.230 +      "set-input-method-active": this._recvSetInputMethodActive.bind(this),
   1.231 +      "deactivate-next-paint-listener": this._deactivateNextPaintListener.bind(this)
   1.232 +    }
   1.233 +
   1.234 +    addMessageListener("browser-element-api:call", function(aMessage) {
   1.235 +      if (aMessage.data.msg_name in mmCalls) {
   1.236 +        return mmCalls[aMessage.data.msg_name].apply(self, arguments);
   1.237 +      }
   1.238 +    });
   1.239 +
   1.240 +    let els = Cc["@mozilla.org/eventlistenerservice;1"]
   1.241 +                .getService(Ci.nsIEventListenerService);
   1.242 +
   1.243 +    // We are using the system group for those events so if something in the
   1.244 +    // content called .stopPropagation() this will still be called.
   1.245 +    els.addSystemEventListener(global, 'keydown',
   1.246 +                               this._keyEventHandler.bind(this),
   1.247 +                               /* useCapture = */ true);
   1.248 +    els.addSystemEventListener(global, 'keypress',
   1.249 +                               this._keyEventHandler.bind(this),
   1.250 +                               /* useCapture = */ true);
   1.251 +    els.addSystemEventListener(global, 'keyup',
   1.252 +                               this._keyEventHandler.bind(this),
   1.253 +                               /* useCapture = */ true);
   1.254 +    els.addSystemEventListener(global, 'DOMWindowClose',
   1.255 +                               this._windowCloseHandler.bind(this),
   1.256 +                               /* useCapture = */ false);
   1.257 +    els.addSystemEventListener(global, 'DOMWindowCreated',
   1.258 +                               this._windowCreatedHandler.bind(this),
   1.259 +                               /* useCapture = */ true);
   1.260 +    els.addSystemEventListener(global, 'DOMWindowResize',
   1.261 +                               this._windowResizeHandler.bind(this),
   1.262 +                               /* useCapture = */ false);
   1.263 +    els.addSystemEventListener(global, 'contextmenu',
   1.264 +                               this._contextmenuHandler.bind(this),
   1.265 +                               /* useCapture = */ false);
   1.266 +    els.addSystemEventListener(global, 'scroll',
   1.267 +                               this._scrollEventHandler.bind(this),
   1.268 +                               /* useCapture = */ false);
   1.269 +
   1.270 +    OBSERVED_EVENTS.forEach((aTopic) => {
   1.271 +      Services.obs.addObserver(this, aTopic, false);
   1.272 +    });
   1.273 +  },
   1.274 +
   1.275 +  observe: function(subject, topic, data) {
   1.276 +    // Ignore notifications not about our document.  (Note that |content| /can/
   1.277 +    // be null; see bug 874900.)
   1.278 +    if (topic !== 'activity-done' && (!content || subject != content.document))
   1.279 +      return;
   1.280 +    if (topic == 'activity-done' && docShell !== subject)
   1.281 +      return;
   1.282 +    switch (topic) {
   1.283 +      case 'fullscreen-origin-change':
   1.284 +        sendAsyncMsg('fullscreen-origin-change', { _payload_: data });
   1.285 +        break;
   1.286 +      case 'ask-parent-to-exit-fullscreen':
   1.287 +        sendAsyncMsg('exit-fullscreen');
   1.288 +        break;
   1.289 +      case 'ask-parent-to-rollback-fullscreen':
   1.290 +        sendAsyncMsg('rollback-fullscreen');
   1.291 +        break;
   1.292 +      case 'activity-done':
   1.293 +        sendAsyncMsg('activitydone', { success: (data == 'activity-success') });
   1.294 +        break;
   1.295 +      case 'xpcom-shutdown':
   1.296 +        this._shuttingDown = true;
   1.297 +        break;
   1.298 +    }
   1.299 +  },
   1.300 +
   1.301 +  /**
   1.302 +   * Called when our TabChildGlobal starts to die.  This is not called when the
   1.303 +   * page inside |content| unloads.
   1.304 +   */
   1.305 +  _unloadHandler: function() {
   1.306 +    this._shuttingDown = true;
   1.307 +    OBSERVED_EVENTS.forEach((aTopic) => {
   1.308 +      Services.obs.removeObserver(this, aTopic);
   1.309 +    });
   1.310 +  },
   1.311 +
   1.312 +  _tryGetInnerWindowID: function(win) {
   1.313 +    let utils = win.QueryInterface(Ci.nsIInterfaceRequestor)
   1.314 +                   .getInterface(Ci.nsIDOMWindowUtils);
   1.315 +    try {
   1.316 +      return utils.currentInnerWindowID;
   1.317 +    }
   1.318 +    catch(e) {
   1.319 +      return null;
   1.320 +    }
   1.321 +  },
   1.322 +
   1.323 +  /**
   1.324 +   * Show a modal prompt.  Called by BrowserElementPromptService.
   1.325 +   */
   1.326 +  showModalPrompt: function(win, args) {
   1.327 +    let utils = win.QueryInterface(Ci.nsIInterfaceRequestor)
   1.328 +                   .getInterface(Ci.nsIDOMWindowUtils);
   1.329 +
   1.330 +    args.windowID = { outer: utils.outerWindowID,
   1.331 +                      inner: this._tryGetInnerWindowID(win) };
   1.332 +    sendAsyncMsg('showmodalprompt', args);
   1.333 +
   1.334 +    let returnValue = this._waitForResult(win);
   1.335 +
   1.336 +    Services.obs.notifyObservers(null, 'BEC:ShownModalPrompt', null);
   1.337 +
   1.338 +    if (args.promptType == 'prompt' ||
   1.339 +        args.promptType == 'confirm' ||
   1.340 +        args.promptType == 'custom-prompt') {
   1.341 +      return returnValue;
   1.342 +    }
   1.343 +  },
   1.344 +
   1.345 +  /**
   1.346 +   * Spin in a nested event loop until we receive a unblock-modal-prompt message for
   1.347 +   * this window.
   1.348 +   */
   1.349 +  _waitForResult: function(win) {
   1.350 +    debug("_waitForResult(" + win + ")");
   1.351 +    let utils = win.QueryInterface(Ci.nsIInterfaceRequestor)
   1.352 +                   .getInterface(Ci.nsIDOMWindowUtils);
   1.353 +
   1.354 +    let outerWindowID = utils.outerWindowID;
   1.355 +    let innerWindowID = this._tryGetInnerWindowID(win);
   1.356 +    if (innerWindowID === null) {
   1.357 +      // I have no idea what waiting for a result means when there's no inner
   1.358 +      // window, so let's just bail.
   1.359 +      debug("_waitForResult: No inner window. Bailing.");
   1.360 +      return;
   1.361 +    }
   1.362 +
   1.363 +    this._windowIDDict[outerWindowID] = Cu.getWeakReference(win);
   1.364 +
   1.365 +    debug("Entering modal state (outerWindowID=" + outerWindowID + ", " +
   1.366 +                                "innerWindowID=" + innerWindowID + ")");
   1.367 +
   1.368 +    utils.enterModalState();
   1.369 +
   1.370 +    // We'll decrement win.modalDepth when we receive a unblock-modal-prompt message
   1.371 +    // for the window.
   1.372 +    if (!win.modalDepth) {
   1.373 +      win.modalDepth = 0;
   1.374 +    }
   1.375 +    win.modalDepth++;
   1.376 +    let origModalDepth = win.modalDepth;
   1.377 +
   1.378 +    let thread = Services.tm.currentThread;
   1.379 +    debug("Nested event loop - begin");
   1.380 +    while (win.modalDepth == origModalDepth && !this._shuttingDown) {
   1.381 +      // Bail out of the loop if the inner window changed; that means the
   1.382 +      // window navigated.  Bail out when we're shutting down because otherwise
   1.383 +      // we'll leak our window.
   1.384 +      if (this._tryGetInnerWindowID(win) !== innerWindowID) {
   1.385 +        debug("_waitForResult: Inner window ID changed " +
   1.386 +              "while in nested event loop.");
   1.387 +        break;
   1.388 +      }
   1.389 +
   1.390 +      thread.processNextEvent(/* mayWait = */ true);
   1.391 +    }
   1.392 +    debug("Nested event loop - finish");
   1.393 +
   1.394 +    // If we exited the loop because the inner window changed, then bail on the
   1.395 +    // modal prompt.
   1.396 +    if (innerWindowID !== this._tryGetInnerWindowID(win)) {
   1.397 +      throw Components.Exception("Modal state aborted by navigation",
   1.398 +                                 Cr.NS_ERROR_NOT_AVAILABLE);
   1.399 +    }
   1.400 +
   1.401 +    let returnValue = win.modalReturnValue;
   1.402 +    delete win.modalReturnValue;
   1.403 +
   1.404 +    if (!this._shuttingDown) {
   1.405 +      utils.leaveModalState();
   1.406 +    }
   1.407 +
   1.408 +    debug("Leaving modal state (outerID=" + outerWindowID + ", " +
   1.409 +                               "innerID=" + innerWindowID + ")");
   1.410 +    return returnValue;
   1.411 +  },
   1.412 +
   1.413 +  _recvStopWaiting: function(msg) {
   1.414 +    let outerID = msg.json.windowID.outer;
   1.415 +    let innerID = msg.json.windowID.inner;
   1.416 +    let returnValue = msg.json.returnValue;
   1.417 +    debug("recvStopWaiting(outer=" + outerID + ", inner=" + innerID +
   1.418 +          ", returnValue=" + returnValue + ")");
   1.419 +
   1.420 +    if (!this._windowIDDict[outerID]) {
   1.421 +      debug("recvStopWaiting: No record of outer window ID " + outerID);
   1.422 +      return;
   1.423 +    }
   1.424 +
   1.425 +    let win = this._windowIDDict[outerID].get();
   1.426 +    delete this._windowIDDict[outerID];
   1.427 +
   1.428 +    if (!win) {
   1.429 +      debug("recvStopWaiting, but window is gone\n");
   1.430 +      return;
   1.431 +    }
   1.432 +
   1.433 +    if (innerID !== this._tryGetInnerWindowID(win)) {
   1.434 +      debug("recvStopWaiting, but inner ID has changed\n");
   1.435 +      return;
   1.436 +    }
   1.437 +
   1.438 +    debug("recvStopWaiting " + win);
   1.439 +    win.modalReturnValue = returnValue;
   1.440 +    win.modalDepth--;
   1.441 +  },
   1.442 +
   1.443 +  _recvExitFullscreen: function() {
   1.444 +    var utils = content.document.defaultView
   1.445 +                       .QueryInterface(Ci.nsIInterfaceRequestor)
   1.446 +                       .getInterface(Ci.nsIDOMWindowUtils);
   1.447 +    utils.exitFullscreen();
   1.448 +  },
   1.449 +
   1.450 +  _titleChangedHandler: function(e) {
   1.451 +    debug("Got titlechanged: (" + e.target.title + ")");
   1.452 +    var win = e.target.defaultView;
   1.453 +
   1.454 +    // Ignore titlechanges which don't come from the top-level
   1.455 +    // <iframe mozbrowser> window.
   1.456 +    if (win == content) {
   1.457 +      sendAsyncMsg('titlechange', { _payload_: e.target.title });
   1.458 +    }
   1.459 +    else {
   1.460 +      debug("Not top level!");
   1.461 +    }
   1.462 +  },
   1.463 +
   1.464 +  _iconChangedHandler: function(e) {
   1.465 +    debug('Got iconchanged: (' + e.target.href + ')');
   1.466 +    let icon = { href: e.target.href };
   1.467 +    if (e.target.getAttribute('sizes')) {
   1.468 +      icon.sizes = e.target.getAttribute('sizes');
   1.469 +    }
   1.470 +
   1.471 +    sendAsyncMsg('iconchange', icon);
   1.472 +  },
   1.473 +
   1.474 +  _openSearchHandler: function(e) {
   1.475 +    debug('Got opensearch: (' + e.target.href + ')');
   1.476 +
   1.477 +    if (e.target.type !== "application/opensearchdescription+xml") {
   1.478 +      return;
   1.479 +    }
   1.480 +
   1.481 +    sendAsyncMsg('opensearch', { title: e.target.title,
   1.482 +                                 href: e.target.href });
   1.483 +
   1.484 +  },
   1.485 +
   1.486 +  _manifestChangedHandler: function(e) {
   1.487 +    debug('Got manifestchanged: (' + e.target.href + ')');
   1.488 +    let manifest = { href: e.target.href };
   1.489 +    sendAsyncMsg('manifestchange', manifest);
   1.490 +
   1.491 +  },
   1.492 +
   1.493 +  // Processes the "rel" field in <link> tags and forward to specific handlers.
   1.494 +  _linkAddedHandler: function(e) {
   1.495 +    let win = e.target.ownerDocument.defaultView;
   1.496 +    // Ignore links which don't come from the top-level
   1.497 +    // <iframe mozbrowser> window.
   1.498 +    if (win != content) {
   1.499 +      debug('Not top level!');
   1.500 +      return;
   1.501 +    }
   1.502 +
   1.503 +    let handlers = {
   1.504 +      'icon': this._iconChangedHandler,
   1.505 +      'search': this._openSearchHandler,
   1.506 +      'manifest': this._manifestChangedHandler
   1.507 +    };
   1.508 +
   1.509 +    debug('Got linkAdded: (' + e.target.href + ') ' + e.target.rel);
   1.510 +    e.target.rel.split(' ').forEach(function(x) {
   1.511 +      let token = x.toLowerCase();
   1.512 +      if (handlers[token]) {
   1.513 +        handlers[token](e);
   1.514 +      }
   1.515 +    }, this);
   1.516 +  },
   1.517 +
   1.518 +  _metaAddedHandler: function(e) {
   1.519 +    let win = e.target.ownerDocument.defaultView;
   1.520 +    // Ignore metas which don't come from the top-level
   1.521 +    // <iframe mozbrowser> window.
   1.522 +    if (win != content) {
   1.523 +      debug('Not top level!');
   1.524 +      return;
   1.525 +    }
   1.526 +
   1.527 +    if (!e.target.name) {
   1.528 +      return;
   1.529 +    }
   1.530 +
   1.531 +    debug('Got metaAdded: (' + e.target.name + ') ' + e.target.content);
   1.532 +    if (e.target.name == 'application-name') {
   1.533 +      let meta = { name: e.target.name,
   1.534 +                   content: e.target.content };
   1.535 +
   1.536 +      let lang;
   1.537 +      let elm;
   1.538 +
   1.539 +      for (elm = e.target;
   1.540 +           !lang && elm && elm.nodeType == e.target.ELEMENT_NODE;
   1.541 +           elm = elm.parentNode) {
   1.542 +        if (elm.hasAttribute('lang')) {
   1.543 +          lang = elm.getAttribute('lang');
   1.544 +          continue;
   1.545 +        }
   1.546 +
   1.547 +        if (elm.hasAttributeNS('http://www.w3.org/XML/1998/namespace', 'lang')) {
   1.548 +          lang = elm.getAttributeNS('http://www.w3.org/XML/1998/namespace', 'lang');
   1.549 +          continue;
   1.550 +        }
   1.551 +      }
   1.552 +
   1.553 +      // No lang has been detected.
   1.554 +      if (!lang && elm.nodeType == e.target.DOCUMENT_NODE) {
   1.555 +        lang = elm.contentLanguage;
   1.556 +      }
   1.557 +
   1.558 +      if (lang) {
   1.559 +        meta.lang = lang;
   1.560 +      }
   1.561 +
   1.562 +      sendAsyncMsg('metachange', meta);
   1.563 +    }
   1.564 +  },
   1.565 +
   1.566 +  _addMozAfterPaintHandler: function(callback) {
   1.567 +    function onMozAfterPaint() {
   1.568 +      let uri = docShell.QueryInterface(Ci.nsIWebNavigation).currentURI;
   1.569 +      if (uri.spec != "about:blank") {
   1.570 +        debug("Got afterpaint event: " + uri.spec);
   1.571 +        removeEventListener('MozAfterPaint', onMozAfterPaint,
   1.572 +                            /* useCapture = */ true);
   1.573 +        callback();
   1.574 +      }
   1.575 +    }
   1.576 +
   1.577 +    addEventListener('MozAfterPaint', onMozAfterPaint, /* useCapture = */ true);
   1.578 +    return onMozAfterPaint;
   1.579 +  },
   1.580 +
   1.581 +  _removeMozAfterPaintHandler: function(listener) {
   1.582 +    removeEventListener('MozAfterPaint', listener,
   1.583 +                        /* useCapture = */ true);
   1.584 +  },
   1.585 +
   1.586 +  _activateNextPaintListener: function(e) {
   1.587 +    if (!this._nextPaintHandler) {
   1.588 +      this._nextPaintHandler = this._addMozAfterPaintHandler(function () {
   1.589 +        this._nextPaintHandler = null;
   1.590 +        sendAsyncMsg('nextpaint');
   1.591 +      }.bind(this));
   1.592 +    }
   1.593 +  },
   1.594 +
   1.595 +  _deactivateNextPaintListener: function(e) {
   1.596 +    if (this._nextPaintHandler) {
   1.597 +      this._removeMozAfterPaintHandler(this._nextPaintHandler);
   1.598 +      this._nextPaintHandler = null;
   1.599 +    }
   1.600 +  },
   1.601 +
   1.602 +  _windowCloseHandler: function(e) {
   1.603 +    let win = e.target;
   1.604 +    if (win != content || e.defaultPrevented) {
   1.605 +      return;
   1.606 +    }
   1.607 +
   1.608 +    debug("Closing window " + win);
   1.609 +    sendAsyncMsg('close');
   1.610 +
   1.611 +    // Inform the window implementation that we handled this close ourselves.
   1.612 +    e.preventDefault();
   1.613 +  },
   1.614 +
   1.615 +  _windowCreatedHandler: function(e) {
   1.616 +    let targetDocShell = e.target.defaultView
   1.617 +          .QueryInterface(Ci.nsIInterfaceRequestor)
   1.618 +          .getInterface(Ci.nsIWebNavigation);
   1.619 +    if (targetDocShell != docShell) {
   1.620 +      return;
   1.621 +    }
   1.622 +
   1.623 +    let uri = docShell.QueryInterface(Ci.nsIWebNavigation).currentURI;
   1.624 +    debug("Window created: " + uri.spec);
   1.625 +    if (uri.spec != "about:blank") {
   1.626 +      this._addMozAfterPaintHandler(function () {
   1.627 +        sendAsyncMsg('documentfirstpaint');
   1.628 +      });
   1.629 +      this._isContentWindowCreated = true;
   1.630 +      // Handle pending SetInputMethodActive request.
   1.631 +      while (this._pendingSetInputMethodActive.length > 0) {
   1.632 +        this._recvSetInputMethodActive(this._pendingSetInputMethodActive.shift());
   1.633 +      }
   1.634 +    }
   1.635 +  },
   1.636 +
   1.637 +  _windowResizeHandler: function(e) {
   1.638 +    let win = e.target;
   1.639 +    if (win != content || e.defaultPrevented) {
   1.640 +      return;
   1.641 +    }
   1.642 +
   1.643 +    debug("resizing window " + win);
   1.644 +    sendAsyncMsg('resize', { width: e.detail.width, height: e.detail.height });
   1.645 +
   1.646 +    // Inform the window implementation that we handled this resize ourselves.
   1.647 +    e.preventDefault();
   1.648 +  },
   1.649 +
   1.650 +  _contextmenuHandler: function(e) {
   1.651 +    debug("Got contextmenu");
   1.652 +
   1.653 +    if (e.defaultPrevented) {
   1.654 +      return;
   1.655 +    }
   1.656 +
   1.657 +    this._ctxCounter++;
   1.658 +    this._ctxHandlers = {};
   1.659 +
   1.660 +    var elem = e.target;
   1.661 +    var menuData = {systemTargets: [], contextmenu: null};
   1.662 +    var ctxMenuId = null;
   1.663 +
   1.664 +    while (elem && elem.parentNode) {
   1.665 +      var ctxData = this._getSystemCtxMenuData(elem);
   1.666 +      if (ctxData) {
   1.667 +        menuData.systemTargets.push({
   1.668 +          nodeName: elem.nodeName,
   1.669 +          data: ctxData
   1.670 +        });
   1.671 +      }
   1.672 +
   1.673 +      if (!ctxMenuId && 'hasAttribute' in elem && elem.hasAttribute('contextmenu')) {
   1.674 +        ctxMenuId = elem.getAttribute('contextmenu');
   1.675 +      }
   1.676 +      elem = elem.parentNode;
   1.677 +    }
   1.678 +
   1.679 +    if (ctxMenuId) {
   1.680 +      var menu = e.target.ownerDocument.getElementById(ctxMenuId);
   1.681 +      if (menu) {
   1.682 +        menuData.contextmenu = this._buildMenuObj(menu, '');
   1.683 +      }
   1.684 +    }
   1.685 +
   1.686 +    // The value returned by the contextmenu sync call is true iff the embedder
   1.687 +    // called preventDefault() on its contextmenu event.
   1.688 +    //
   1.689 +    // We call preventDefault() on our contextmenu event iff the embedder called
   1.690 +    // preventDefault() on /its/ contextmenu event.  This way, if the embedder
   1.691 +    // ignored the contextmenu event, TabChild will fire a click.
   1.692 +    if (sendSyncMsg('contextmenu', menuData)[0]) {
   1.693 +      e.preventDefault();
   1.694 +    } else {
   1.695 +      this._ctxHandlers = {};
   1.696 +    }
   1.697 +  },
   1.698 +
   1.699 +  _getSystemCtxMenuData: function(elem) {
   1.700 +    if ((elem instanceof Ci.nsIDOMHTMLAnchorElement && elem.href) ||
   1.701 +        (elem instanceof Ci.nsIDOMHTMLAreaElement && elem.href)) {
   1.702 +      return {uri: elem.href};
   1.703 +    }
   1.704 +    if (elem instanceof Ci.nsIImageLoadingContent && elem.currentURI) {
   1.705 +      return {uri: elem.currentURI.spec};
   1.706 +    }
   1.707 +    if (elem instanceof Ci.nsIDOMHTMLImageElement) {
   1.708 +      return {uri: elem.src};
   1.709 +    }
   1.710 +    if (elem instanceof Ci.nsIDOMHTMLMediaElement) {
   1.711 +      let hasVideo = !(elem.readyState >= elem.HAVE_METADATA &&
   1.712 +                       (elem.videoWidth == 0 || elem.videoHeight == 0));
   1.713 +      return {uri: elem.currentSrc || elem.src, hasVideo: hasVideo};
   1.714 +    }
   1.715 +    return false;
   1.716 +  },
   1.717 +
   1.718 +  _scrollEventHandler: function(e) {
   1.719 +    let win = e.target.defaultView;
   1.720 +    if (win != content) {
   1.721 +      return;
   1.722 +    }
   1.723 +
   1.724 +    debug("scroll event " + win);
   1.725 +    sendAsyncMsg("scroll", { top: win.scrollY, left: win.scrollX });
   1.726 +  },
   1.727 +
   1.728 +  _recvPurgeHistory: function(data) {
   1.729 +    debug("Received purgeHistory message: (" + data.json.id + ")");
   1.730 +
   1.731 +    let history = docShell.QueryInterface(Ci.nsIWebNavigation).sessionHistory;
   1.732 +
   1.733 +    try {
   1.734 +      if (history && history.count) {
   1.735 +        history.PurgeHistory(history.count);
   1.736 +      }
   1.737 +    } catch(e) {}
   1.738 +
   1.739 +    sendAsyncMsg('got-purge-history', { id: data.json.id, successRv: true });
   1.740 +  },
   1.741 +
   1.742 +  _recvGetScreenshot: function(data) {
   1.743 +    debug("Received getScreenshot message: (" + data.json.id + ")");
   1.744 +
   1.745 +    let self = this;
   1.746 +    let maxWidth = data.json.args.width;
   1.747 +    let maxHeight = data.json.args.height;
   1.748 +    let mimeType = data.json.args.mimeType;
   1.749 +    let domRequestID = data.json.id;
   1.750 +
   1.751 +    let takeScreenshotClosure = function() {
   1.752 +      self._takeScreenshot(maxWidth, maxHeight, mimeType, domRequestID);
   1.753 +    };
   1.754 +
   1.755 +    let maxDelayMS = 2000;
   1.756 +    try {
   1.757 +      maxDelayMS = Services.prefs.getIntPref('dom.browserElement.maxScreenshotDelayMS');
   1.758 +    }
   1.759 +    catch(e) {}
   1.760 +
   1.761 +    // Try to wait for the event loop to go idle before we take the screenshot,
   1.762 +    // but once we've waited maxDelayMS milliseconds, go ahead and take it
   1.763 +    // anyway.
   1.764 +    Cc['@mozilla.org/message-loop;1'].getService(Ci.nsIMessageLoop).postIdleTask(
   1.765 +      takeScreenshotClosure, maxDelayMS);
   1.766 +  },
   1.767 +
   1.768 +  /**
   1.769 +   * Actually take a screenshot and foward the result up to our parent, given
   1.770 +   * the desired maxWidth and maxHeight (in CSS pixels), and given the
   1.771 +   * DOMRequest ID associated with the request from the parent.
   1.772 +   */
   1.773 +  _takeScreenshot: function(maxWidth, maxHeight, mimeType, domRequestID) {
   1.774 +    // You can think of the screenshotting algorithm as carrying out the
   1.775 +    // following steps:
   1.776 +    //
   1.777 +    // - Calculate maxWidth, maxHeight, and viewport's width and height in the
   1.778 +    //   dimension of device pixels by multiply the numbers with
   1.779 +    //   window.devicePixelRatio.
   1.780 +    //
   1.781 +    // - Let scaleWidth be the factor by which we'd need to downscale the
   1.782 +    //   viewport pixel width so it would fit within maxPixelWidth.
   1.783 +    //   (If the viewport's pixel width is less than maxPixelWidth, let
   1.784 +    //   scaleWidth be 1.) Compute scaleHeight the same way.
   1.785 +    //
   1.786 +    // - Scale the viewport by max(scaleWidth, scaleHeight).  Now either the
   1.787 +    //   viewport's width is no larger than maxWidth, the viewport's height is
   1.788 +    //   no larger than maxHeight, or both.
   1.789 +    //
   1.790 +    // - Crop the viewport so its width is no larger than maxWidth and its
   1.791 +    //   height is no larger than maxHeight.
   1.792 +    //
   1.793 +    // - Set mozOpaque to true and background color to solid white
   1.794 +    //   if we are taking a JPEG screenshot, keep transparent if otherwise.
   1.795 +    //
   1.796 +    // - Return a screenshot of the page's viewport scaled and cropped per
   1.797 +    //   above.
   1.798 +    debug("Taking a screenshot: maxWidth=" + maxWidth +
   1.799 +          ", maxHeight=" + maxHeight +
   1.800 +          ", mimeType=" + mimeType +
   1.801 +          ", domRequestID=" + domRequestID + ".");
   1.802 +
   1.803 +    if (!content) {
   1.804 +      // If content is not loaded yet, bail out since even sendAsyncMessage
   1.805 +      // fails...
   1.806 +      debug("No content yet!");
   1.807 +      return;
   1.808 +    }
   1.809 +
   1.810 +    let devicePixelRatio = content.devicePixelRatio;
   1.811 +
   1.812 +    let maxPixelWidth = Math.round(maxWidth * devicePixelRatio);
   1.813 +    let maxPixelHeight = Math.round(maxHeight * devicePixelRatio);
   1.814 +
   1.815 +    let contentPixelWidth = content.innerWidth * devicePixelRatio;
   1.816 +    let contentPixelHeight = content.innerHeight * devicePixelRatio;
   1.817 +
   1.818 +    let scaleWidth = Math.min(1, maxPixelWidth / contentPixelWidth);
   1.819 +    let scaleHeight = Math.min(1, maxPixelHeight / contentPixelHeight);
   1.820 +
   1.821 +    let scale = Math.max(scaleWidth, scaleHeight);
   1.822 +
   1.823 +    let canvasWidth =
   1.824 +      Math.min(maxPixelWidth, Math.round(contentPixelWidth * scale));
   1.825 +    let canvasHeight =
   1.826 +      Math.min(maxPixelHeight, Math.round(contentPixelHeight * scale));
   1.827 +
   1.828 +    let transparent = (mimeType !== 'image/jpeg');
   1.829 +
   1.830 +    var canvas = content.document
   1.831 +      .createElementNS("http://www.w3.org/1999/xhtml", "canvas");
   1.832 +    if (!transparent)
   1.833 +      canvas.mozOpaque = true;
   1.834 +    canvas.width = canvasWidth;
   1.835 +    canvas.height = canvasHeight;
   1.836 +
   1.837 +    let ctx = canvas.getContext("2d", { willReadFrequently: true });
   1.838 +    ctx.scale(scale * devicePixelRatio, scale * devicePixelRatio);
   1.839 +
   1.840 +    let flags = ctx.DRAWWINDOW_DRAW_VIEW |
   1.841 +                ctx.DRAWWINDOW_USE_WIDGET_LAYERS |
   1.842 +                ctx.DRAWWINDOW_DO_NOT_FLUSH |
   1.843 +                ctx.DRAWWINDOW_ASYNC_DECODE_IMAGES;
   1.844 +    ctx.drawWindow(content, 0, 0, content.innerWidth, content.innerHeight,
   1.845 +                   transparent ? "rgba(255,255,255,0)" : "rgb(255,255,255)",
   1.846 +                   flags);
   1.847 +
   1.848 +    // Take a JPEG screenshot by default instead of PNG with alpha channel.
   1.849 +    // This requires us to unpremultiply the alpha channel, which
   1.850 +    // is expensive on ARM processors because they lack a hardware integer
   1.851 +    // division instruction.
   1.852 +    canvas.toBlob(function(blob) {
   1.853 +      sendAsyncMsg('got-screenshot', {
   1.854 +        id: domRequestID,
   1.855 +        successRv: blob
   1.856 +      });
   1.857 +    }, mimeType);
   1.858 +  },
   1.859 +
   1.860 +  _recvFireCtxCallback: function(data) {
   1.861 +    debug("Received fireCtxCallback message: (" + data.json.menuitem + ")");
   1.862 +    // We silently ignore if the embedder uses an incorrect id in the callback
   1.863 +    if (data.json.menuitem in this._ctxHandlers) {
   1.864 +      this._ctxHandlers[data.json.menuitem].click();
   1.865 +      this._ctxHandlers = {};
   1.866 +    } else {
   1.867 +      debug("Ignored invalid contextmenu invocation");
   1.868 +    }
   1.869 +  },
   1.870 +
   1.871 +  _buildMenuObj: function(menu, idPrefix) {
   1.872 +    function maybeCopyAttribute(src, target, attribute) {
   1.873 +      if (src.getAttribute(attribute)) {
   1.874 +        target[attribute] = src.getAttribute(attribute);
   1.875 +      }
   1.876 +    }
   1.877 +
   1.878 +    var menuObj = {type: 'menu', items: []};
   1.879 +    maybeCopyAttribute(menu, menuObj, 'label');
   1.880 +
   1.881 +    for (var i = 0, child; child = menu.children[i++];) {
   1.882 +      if (child.nodeName === 'MENU') {
   1.883 +        menuObj.items.push(this._buildMenuObj(child, idPrefix + i + '_'));
   1.884 +      } else if (child.nodeName === 'MENUITEM') {
   1.885 +        var id = this._ctxCounter + '_' + idPrefix + i;
   1.886 +        var menuitem = {id: id, type: 'menuitem'};
   1.887 +        maybeCopyAttribute(child, menuitem, 'label');
   1.888 +        maybeCopyAttribute(child, menuitem, 'icon');
   1.889 +        this._ctxHandlers[id] = child;
   1.890 +        menuObj.items.push(menuitem);
   1.891 +      }
   1.892 +    }
   1.893 +    return menuObj;
   1.894 +  },
   1.895 +
   1.896 +  _recvSetVisible: function(data) {
   1.897 +    debug("Received setVisible message: (" + data.json.visible + ")");
   1.898 +    if (this._forcedVisible == data.json.visible) {
   1.899 +      return;
   1.900 +    }
   1.901 +
   1.902 +    this._forcedVisible = data.json.visible;
   1.903 +    this._updateVisibility();
   1.904 +  },
   1.905 +
   1.906 +  _recvVisible: function(data) {
   1.907 +    sendAsyncMsg('got-visible', {
   1.908 +      id: data.json.id,
   1.909 +      successRv: docShell.isActive
   1.910 +    });
   1.911 +  },
   1.912 +
   1.913 +  /**
   1.914 +   * Called when the window which contains this iframe becomes hidden or
   1.915 +   * visible.
   1.916 +   */
   1.917 +  _recvOwnerVisibilityChange: function(data) {
   1.918 +    debug("Received ownerVisibilityChange: (" + data.json.visible + ")");
   1.919 +    this._ownerVisible = data.json.visible;
   1.920 +    this._updateVisibility();
   1.921 +  },
   1.922 +
   1.923 +  _updateVisibility: function() {
   1.924 +    var visible = this._forcedVisible && this._ownerVisible;
   1.925 +    if (docShell.isActive !== visible) {
   1.926 +      docShell.isActive = visible;
   1.927 +      sendAsyncMsg('visibilitychange', {visible: visible});
   1.928 +    }
   1.929 +  },
   1.930 +
   1.931 +  _recvSendMouseEvent: function(data) {
   1.932 +    let json = data.json;
   1.933 +    let utils = content.QueryInterface(Ci.nsIInterfaceRequestor)
   1.934 +                       .getInterface(Ci.nsIDOMWindowUtils);
   1.935 +    utils.sendMouseEventToWindow(json.type, json.x, json.y, json.button,
   1.936 +                                 json.clickCount, json.modifiers);
   1.937 +  },
   1.938 +
   1.939 +  _recvSendTouchEvent: function(data) {
   1.940 +    let json = data.json;
   1.941 +    let utils = content.QueryInterface(Ci.nsIInterfaceRequestor)
   1.942 +                       .getInterface(Ci.nsIDOMWindowUtils);
   1.943 +    utils.sendTouchEventToWindow(json.type, json.identifiers, json.touchesX,
   1.944 +                                 json.touchesY, json.radiisX, json.radiisY,
   1.945 +                                 json.rotationAngles, json.forces, json.count,
   1.946 +                                 json.modifiers);
   1.947 +  },
   1.948 +
   1.949 +  _recvCanGoBack: function(data) {
   1.950 +    var webNav = docShell.QueryInterface(Ci.nsIWebNavigation);
   1.951 +    sendAsyncMsg('got-can-go-back', {
   1.952 +      id: data.json.id,
   1.953 +      successRv: webNav.canGoBack
   1.954 +    });
   1.955 +  },
   1.956 +
   1.957 +  _recvCanGoForward: function(data) {
   1.958 +    var webNav = docShell.QueryInterface(Ci.nsIWebNavigation);
   1.959 +    sendAsyncMsg('got-can-go-forward', {
   1.960 +      id: data.json.id,
   1.961 +      successRv: webNav.canGoForward
   1.962 +    });
   1.963 +  },
   1.964 +
   1.965 +  _recvGoBack: function(data) {
   1.966 +    try {
   1.967 +      docShell.QueryInterface(Ci.nsIWebNavigation).goBack();
   1.968 +    } catch(e) {
   1.969 +      // Silently swallow errors; these happen when we can't go back.
   1.970 +    }
   1.971 +  },
   1.972 +
   1.973 +  _recvGoForward: function(data) {
   1.974 +    try {
   1.975 +      docShell.QueryInterface(Ci.nsIWebNavigation).goForward();
   1.976 +    } catch(e) {
   1.977 +      // Silently swallow errors; these happen when we can't go forward.
   1.978 +    }
   1.979 +  },
   1.980 +
   1.981 +  _recvReload: function(data) {
   1.982 +    let webNav = docShell.QueryInterface(Ci.nsIWebNavigation);
   1.983 +    let reloadFlags = data.json.hardReload ?
   1.984 +      webNav.LOAD_FLAGS_BYPASS_PROXY | webNav.LOAD_FLAGS_BYPASS_CACHE :
   1.985 +      webNav.LOAD_FLAGS_NONE;
   1.986 +    try {
   1.987 +      webNav.reload(reloadFlags);
   1.988 +    } catch(e) {
   1.989 +      // Silently swallow errors; these can happen if a used cancels reload
   1.990 +    }
   1.991 +  },
   1.992 +
   1.993 +  _recvStop: function(data) {
   1.994 +    let webNav = docShell.QueryInterface(Ci.nsIWebNavigation);
   1.995 +    webNav.stop(webNav.STOP_NETWORK);
   1.996 +  },
   1.997 +
   1.998 +  _recvSetInputMethodActive: function(data) {
   1.999 +    let msgData = { id: data.json.id };
  1.1000 +    if (!this._isContentWindowCreated) {
  1.1001 +      if (data.json.args.isActive) {
  1.1002 +        // To activate the input method, we should wait before the content
  1.1003 +        // window is ready.
  1.1004 +        this._pendingSetInputMethodActive.push(data);
  1.1005 +        return;
  1.1006 +      }
  1.1007 +      msgData.successRv = null;
  1.1008 +      sendAsyncMsg('got-set-input-method-active', msgData);
  1.1009 +      return;
  1.1010 +    }
  1.1011 +    // Unwrap to access webpage content.
  1.1012 +    let nav = XPCNativeWrapper.unwrap(content.document.defaultView.navigator);
  1.1013 +    if (nav.mozInputMethod) {
  1.1014 +      // Wrap to access the chrome-only attribute setActive.
  1.1015 +      new XPCNativeWrapper(nav.mozInputMethod).setActive(data.json.args.isActive);
  1.1016 +      msgData.successRv = null;
  1.1017 +    } else {
  1.1018 +      msgData.errorMsg = 'Cannot access mozInputMethod.';
  1.1019 +    }
  1.1020 +    sendAsyncMsg('got-set-input-method-active', msgData);
  1.1021 +  },
  1.1022 +
  1.1023 +  _keyEventHandler: function(e) {
  1.1024 +    if (whitelistedEvents.indexOf(e.keyCode) != -1 && !e.defaultPrevented) {
  1.1025 +      sendAsyncMsg('keyevent', {
  1.1026 +        type: e.type,
  1.1027 +        keyCode: e.keyCode,
  1.1028 +        charCode: e.charCode,
  1.1029 +      });
  1.1030 +    }
  1.1031 +  },
  1.1032 +
  1.1033 +  // The docShell keeps a weak reference to the progress listener, so we need
  1.1034 +  // to keep a strong ref to it ourselves.
  1.1035 +  _progressListener: {
  1.1036 +    QueryInterface: XPCOMUtils.generateQI([Ci.nsIWebProgressListener,
  1.1037 +                                           Ci.nsISupportsWeakReference]),
  1.1038 +    _seenLoadStart: false,
  1.1039 +
  1.1040 +    onLocationChange: function(webProgress, request, location, flags) {
  1.1041 +      // We get progress events from subshells here, which is kind of weird.
  1.1042 +      if (webProgress != docShell) {
  1.1043 +        return;
  1.1044 +      }
  1.1045 +
  1.1046 +      // Ignore locationchange events which occur before the first loadstart.
  1.1047 +      // These are usually about:blank loads we don't care about.
  1.1048 +      if (!this._seenLoadStart) {
  1.1049 +        return;
  1.1050 +      }
  1.1051 +
  1.1052 +      // Remove password and wyciwyg from uri.
  1.1053 +      location = Cc["@mozilla.org/docshell/urifixup;1"]
  1.1054 +        .getService(Ci.nsIURIFixup).createExposableURI(location);
  1.1055 +
  1.1056 +      sendAsyncMsg('locationchange', { _payload_: location.spec });
  1.1057 +    },
  1.1058 +
  1.1059 +    onStateChange: function(webProgress, request, stateFlags, status) {
  1.1060 +      if (webProgress != docShell) {
  1.1061 +        return;
  1.1062 +      }
  1.1063 +
  1.1064 +      if (stateFlags & Ci.nsIWebProgressListener.STATE_START) {
  1.1065 +        this._seenLoadStart = true;
  1.1066 +        sendAsyncMsg('loadstart');
  1.1067 +      }
  1.1068 +
  1.1069 +      if (stateFlags & Ci.nsIWebProgressListener.STATE_STOP) {
  1.1070 +        let bgColor = 'transparent';
  1.1071 +        try {
  1.1072 +          bgColor = content.getComputedStyle(content.document.body)
  1.1073 +                           .getPropertyValue('background-color');
  1.1074 +        } catch (e) {}
  1.1075 +        sendAsyncMsg('loadend', {backgroundColor: bgColor});
  1.1076 +
  1.1077 +        // Ignoring NS_BINDING_ABORTED, which is set when loading page is
  1.1078 +        // stopped.
  1.1079 +        if (status == Cr.NS_OK ||
  1.1080 +            status == Cr.NS_BINDING_ABORTED) {
  1.1081 +          return;
  1.1082 +        }
  1.1083 +
  1.1084 +        if (NS_ERROR_GET_MODULE(status) == NS_ERROR_MODULE_SECURITY &&
  1.1085 +            getErrorClass(status) == Ci.nsINSSErrorsService.ERROR_CLASS_BAD_CERT) {
  1.1086 +
  1.1087 +          // XXX Is there a point firing the event if the error page is not
  1.1088 +          // certerror? If yes, maybe we should add a property to the
  1.1089 +          // event to to indicate whether there is a custom page. That would
  1.1090 +          // let the embedder have more control over the desired behavior.
  1.1091 +          var errorPage = null;
  1.1092 +          try {
  1.1093 +            errorPage = Services.prefs.getCharPref(CERTIFICATE_ERROR_PAGE_PREF);
  1.1094 +          } catch(e) {}
  1.1095 +
  1.1096 +          if (errorPage == 'certerror') {
  1.1097 +            sendAsyncMsg('error', { type: 'certerror' });
  1.1098 +            return;
  1.1099 +          }
  1.1100 +        }
  1.1101 +
  1.1102 +        // TODO See nsDocShell::DisplayLoadError for a list of all the error
  1.1103 +        // codes (the status param) we should eventually handle here.
  1.1104 +        sendAsyncMsg('error', { type: 'other' });
  1.1105 +      }
  1.1106 +    },
  1.1107 +
  1.1108 +    onSecurityChange: function(webProgress, request, state) {
  1.1109 +      if (webProgress != docShell) {
  1.1110 +        return;
  1.1111 +      }
  1.1112 +
  1.1113 +      var stateDesc;
  1.1114 +      if (state & Ci.nsIWebProgressListener.STATE_IS_SECURE) {
  1.1115 +        stateDesc = 'secure';
  1.1116 +      }
  1.1117 +      else if (state & Ci.nsIWebProgressListener.STATE_IS_BROKEN) {
  1.1118 +        stateDesc = 'broken';
  1.1119 +      }
  1.1120 +      else if (state & Ci.nsIWebProgressListener.STATE_IS_INSECURE) {
  1.1121 +        stateDesc = 'insecure';
  1.1122 +      }
  1.1123 +      else {
  1.1124 +        debug("Unexpected securitychange state!");
  1.1125 +        stateDesc = '???';
  1.1126 +      }
  1.1127 +
  1.1128 +      // XXX Until bug 764496 is fixed, this will always return false.
  1.1129 +      var isEV = !!(state & Ci.nsIWebProgressListener.STATE_IDENTITY_EV_TOPLEVEL);
  1.1130 +
  1.1131 +      sendAsyncMsg('securitychange', { state: stateDesc, extendedValidation: isEV });
  1.1132 +    },
  1.1133 +
  1.1134 +    onStatusChange: function(webProgress, request, status, message) {},
  1.1135 +    onProgressChange: function(webProgress, request, curSelfProgress,
  1.1136 +                               maxSelfProgress, curTotalProgress, maxTotalProgress) {},
  1.1137 +  },
  1.1138 +
  1.1139 +  // Expose the message manager for WebApps and others.
  1.1140 +  _messageManagerPublic: {
  1.1141 +    sendAsyncMessage: global.sendAsyncMessage.bind(global),
  1.1142 +    sendSyncMessage: global.sendSyncMessage.bind(global),
  1.1143 +    addMessageListener: global.addMessageListener.bind(global),
  1.1144 +    removeMessageListener: global.removeMessageListener.bind(global)
  1.1145 +  },
  1.1146 +
  1.1147 +  get messageManager() {
  1.1148 +    return this._messageManagerPublic;
  1.1149 +  }
  1.1150 +};
  1.1151 +
  1.1152 +var api = new BrowserElementChild();
  1.1153 +

mercurial