summaryrefslogtreecommitdiff
path: root/toolkit/identity/Sandbox.jsm
blob: 68757c212fea614d18e179d9b563a2e2ef82083b (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
 * You can obtain one at http://mozilla.org/MPL/2.0/. */

"use strict";

this.EXPORTED_SYMBOLS = ["Sandbox"];

const {classes: Cc, interfaces: Ci, utils: Cu} = Components;

const XHTML_NS = "http://www.w3.org/1999/xhtml";

Cu.import("resource://gre/modules/XPCOMUtils.jsm");
Cu.import("resource://gre/modules/Services.jsm");

XPCOMUtils.defineLazyModuleGetter(this,
                                  "Logger",
                                  "resource://gre/modules/identity/LogUtils.jsm");

/**
 * An object that represents a sandbox in an iframe loaded with aURL. The
 * callback provided to the constructor will be invoked when the sandbox is
 * ready to be used. The callback will receive this object as its only argument.
 *
 * You must call free() when you are finished with the sandbox to explicitly
 * free up all associated resources.
 *
 * @param aURL
 *        (string) URL to load in the sandbox.
 *
 * @param aCallback
 *        (function) Callback to be invoked with a Sandbox, when ready.
 */
this.Sandbox = function Sandbox(aURL, aCallback) {
  // Normalize the URL so the comparison in _makeSandboxContentLoaded works
  this._url = Services.io.newURI(aURL, null, null).spec;
  this._log("Creating sandbox for:", this._url);
  this._createFrame();
  this._createSandbox(aCallback);
};

this.Sandbox.prototype = {

  /**
   * Use the outer window ID as the identifier of the sandbox.
   */
  get id() {
    return this._frame.contentWindow.QueryInterface(Ci.nsIInterfaceRequestor)
               .getInterface(Ci.nsIDOMWindowUtils).outerWindowID;
  },

  /**
   * Reload the URL in the sandbox. This is useful to reuse a Sandbox (same
   * id and URL).
   */
  reload: function Sandbox_reload(aCallback) {
    this._log("reload:", this.id, ":", this._url);
    this._createSandbox(function createdSandbox(aSandbox) {
      this._log("reloaded sandbox id:", aSandbox.id);
      aCallback(aSandbox);
    }.bind(this));
  },

  /**
   * Frees the sandbox and releases the iframe created to host it.
   */
  free: function Sandbox_free() {
    this._log("free:", this.id);
    this._container.removeChild(this._frame);
    this._frame = null;
    this._container = null;
    this._url = null;
  },

  /**
   * Creates an empty, hidden iframe and sets it to the _frame
   * property of this object.
   */
  _createFrame: function Sandbox__createFrame() {
    let hiddenWindow = Services.appShell.hiddenDOMWindow;
    let doc = hiddenWindow.document;

    // Insert iframe in to create docshell.
    let frame = doc.createElementNS(XHTML_NS, "iframe");
    frame.setAttribute("mozframetype", "content");
    frame.sandbox = "allow-forms allow-scripts allow-same-origin";
    frame.style.visibility = "collapse";
    doc.documentElement.appendChild(frame);

    let docShell = frame.contentWindow.QueryInterface(Ci.nsIInterfaceRequestor)
                                      .getInterface(Ci.nsIWebNavigation)
                                      .QueryInterface(Ci.nsIInterfaceRequestor)
                                      .getInterface(Ci.nsIDocShell);

    // Stop about:blank from being loaded.
    docShell.stop(Ci.nsIWebNavigation.STOP_NETWORK);

    // Disable some types of content
    docShell.allowAuth = false;
    docShell.allowPlugins = false;
    docShell.allowImages = false;
    docShell.allowMedia = false;
    docShell.allowWindowControl = false;

    // Disable stylesheet loading since the document is not visible.
    let markupDocViewer = docShell.contentViewer;
    markupDocViewer.authorStyleDisabled = true;

    // Set instance properties.
    this._frame = frame;
    this._container = doc.documentElement;
  },

  _createSandbox: function Sandbox__createSandbox(aCallback) {
    let self = this;
    function _makeSandboxContentLoaded(event) {
      self._log("_makeSandboxContentLoaded:", self.id,
                event.target.location.toString());
      if (event.target != self._frame.contentDocument) {
        return;
      }
      self._frame.removeEventListener(
        "DOMWindowCreated", _makeSandboxContentLoaded, true
      );

      aCallback(self);
    }

    this._frame.addEventListener("DOMWindowCreated",
                                 _makeSandboxContentLoaded,
                                 true);

    // Load the iframe.
    let webNav = this._frame.contentWindow
                            .QueryInterface(Ci.nsIInterfaceRequestor)
                            .getInterface(Ci.nsIWebNavigation);

    webNav.loadURI(
      this._url,
      Ci.nsIWebNavigation.LOAD_FLAGS_BYPASS_CACHE,
      null, // referrer
      null, // postData
      null  // headers
    );

  },

  _log: function Sandbox__log(...aMessageArgs) {
    Logger.log.apply(Logger, ["sandbox"].concat(aMessageArgs));
  },

};