michael@0: /* This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this michael@0: * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: "use strict"; michael@0: michael@0: module.metadata = { michael@0: "stability": "experimental" michael@0: }; michael@0: michael@0: const { Cc, Ci } = require("chrome"); michael@0: const errors = require("../deprecated/errors"); michael@0: const { Class } = require("../core/heritage"); michael@0: const { List, addListItem, removeListItem } = require("../util/list"); michael@0: const { EventTarget } = require("../event/target"); michael@0: const { emit } = require("../event/core"); michael@0: const { create: makeFrame } = require("./utils"); michael@0: const { defer } = require("../core/promise"); michael@0: const { when: unload } = require("../system/unload"); michael@0: const { validateOptions, getTypeOf } = require("../deprecated/api-utils"); michael@0: const { window } = require("../addon/window"); michael@0: const { fromIterator } = require("../util/array"); michael@0: michael@0: // This cache is used to access friend properties between functions michael@0: // without exposing them on the public API. michael@0: let cache = new Set(); michael@0: let elements = new WeakMap(); michael@0: michael@0: function contentLoaded(target) { michael@0: var deferred = defer(); michael@0: target.addEventListener("DOMContentLoaded", function DOMContentLoaded(event) { michael@0: // "DOMContentLoaded" events from nested frames propagate up to target, michael@0: // ignore events unless it's DOMContentLoaded for the given target. michael@0: if (event.target === target || event.target === target.contentDocument) { michael@0: target.removeEventListener("DOMContentLoaded", DOMContentLoaded, false); michael@0: deferred.resolve(target); michael@0: } michael@0: }, false); michael@0: return deferred.promise; michael@0: } michael@0: michael@0: function FrameOptions(options) { michael@0: options = options || {} michael@0: return validateOptions(options, FrameOptions.validator); michael@0: } michael@0: FrameOptions.validator = { michael@0: onReady: { michael@0: is: ["undefined", "function", "array"], michael@0: ok: function(v) { michael@0: if (getTypeOf(v) === "array") { michael@0: // make sure every item is a function michael@0: return v.every(function (item) typeof(item) === "function") michael@0: } michael@0: return true; michael@0: } michael@0: }, michael@0: onUnload: { michael@0: is: ["undefined", "function"] michael@0: } michael@0: }; michael@0: michael@0: var HiddenFrame = Class({ michael@0: extends: EventTarget, michael@0: initialize: function initialize(options) { michael@0: options = FrameOptions(options); michael@0: EventTarget.prototype.initialize.call(this, options); michael@0: }, michael@0: get element() { michael@0: return elements.get(this); michael@0: }, michael@0: toString: function toString() { michael@0: return "[object Frame]" michael@0: } michael@0: }); michael@0: exports.HiddenFrame = HiddenFrame michael@0: michael@0: function addHidenFrame(frame) { michael@0: if (!(frame instanceof HiddenFrame)) michael@0: throw Error("The object to be added must be a HiddenFrame."); michael@0: michael@0: // This instance was already added. michael@0: if (cache.has(frame)) return frame; michael@0: else cache.add(frame); michael@0: michael@0: let element = makeFrame(window.document, { michael@0: nodeName: "iframe", michael@0: type: "content", michael@0: allowJavascript: true, michael@0: allowPlugins: true, michael@0: allowAuth: true, michael@0: }); michael@0: elements.set(frame, element); michael@0: michael@0: contentLoaded(element).then(function onFrameReady(element) { michael@0: emit(frame, "ready"); michael@0: }, console.exception); michael@0: michael@0: return frame; michael@0: } michael@0: exports.add = addHidenFrame michael@0: michael@0: function removeHiddenFrame(frame) { michael@0: if (!(frame instanceof HiddenFrame)) michael@0: throw Error("The object to be removed must be a HiddenFrame."); michael@0: michael@0: if (!cache.has(frame)) return; michael@0: michael@0: // Remove from cache before calling in order to avoid loop michael@0: cache.delete(frame); michael@0: emit(frame, "unload") michael@0: let element = frame.element michael@0: if (element) element.parentNode.removeChild(element) michael@0: } michael@0: exports.remove = removeHiddenFrame; michael@0: michael@0: unload(function() fromIterator(cache).forEach(removeHiddenFrame));