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: "use strict"; michael@0: michael@0: const { Ci } = require("chrome"); michael@0: const { InputPort } = require("./system"); michael@0: const { getFrameElement, getOuterId, michael@0: getOwnerBrowserWindow } = require("../window/utils"); michael@0: const { isnt } = require("../lang/functional"); michael@0: const { foldp, lift, merges, keepIf } = require("../event/utils"); michael@0: const { object } = require("../util/sequence"); michael@0: const { compose } = require("../lang/functional"); michael@0: const { LastClosed } = require("./browser"); michael@0: const { patch } = require("diffpatcher/index"); michael@0: michael@0: const Document = Ci.nsIDOMDocument; michael@0: michael@0: const isntNull = isnt(null); michael@0: michael@0: const frameID = frame => frame.id; michael@0: const browserID = compose(getOuterId, getOwnerBrowserWindow); michael@0: michael@0: const isInnerFrame = frame => michael@0: frame && frame.hasAttribute("data-is-sdk-inner-frame"); michael@0: michael@0: // Utility function that given content window loaded in our frame views returns michael@0: // an actual frame. This basically takes care of fact that actual frame document michael@0: // is loaded in the nested iframe. If content window is not loaded in the nested michael@0: // frame of the frame view it returs null. michael@0: const getFrame = document => michael@0: document && document.defaultView && getFrameElement(document.defaultView); michael@0: michael@0: const FrameInput = function(options) { michael@0: const input = keepIf(isInnerFrame, null, michael@0: lift(getFrame, new InputPort(options))); michael@0: return lift(frame => { michael@0: if (!frame) return frame; michael@0: const [id, owner] = [frameID(frame), browserID(frame)]; michael@0: return object([id, {owners: object([owner, options.update])}]); michael@0: }, input); michael@0: }; michael@0: michael@0: const LastLoading = new FrameInput({topic: "document-element-inserted", michael@0: update: {readyState: "loading"}}); michael@0: exports.LastLoading = LastLoading; michael@0: michael@0: const LastInteractive = new FrameInput({topic: "content-document-interactive", michael@0: update: {readyState: "interactive"}}); michael@0: exports.LastInteractive = LastInteractive; michael@0: michael@0: const LastLoaded = new FrameInput({topic: "content-document-loaded", michael@0: update: {readyState: "complete"}}); michael@0: exports.LastLoaded = LastLoaded; michael@0: michael@0: const LastUnloaded = new FrameInput({topic: "content-page-hidden", michael@0: update: null}); michael@0: exports.LastUnloaded = LastUnloaded; michael@0: michael@0: // Represents state of SDK frames in form of data structure: michael@0: // {"frame#1": {"id": "frame#1", michael@0: // "inbox": {"data": "ping", michael@0: // "target": {"id": "frame#1", "owner": "outerWindowID#2"}, michael@0: // "source": {"id": "frame#1"}} michael@0: // "url": "resource://addon-1/data/index.html", michael@0: // "owners": {"outerWindowID#1": {"readyState": "loading"}, michael@0: // "outerWindowID#2": {"readyState": "complete"}} michael@0: // michael@0: // michael@0: // frame#2: {"id": "frame#2", michael@0: // "url": "resource://addon-1/data/main.html", michael@0: // "outbox": {"data": "pong", michael@0: // "source": {"id": "frame#2", "owner": "outerWindowID#1"} michael@0: // "target": {"id": "frame#2"}} michael@0: // "owners": {outerWindowID#1: {readyState: "interacitve"}}}} michael@0: const Frames = foldp(patch, {}, merges([ michael@0: LastLoading, michael@0: LastInteractive, michael@0: LastLoaded, michael@0: LastUnloaded, michael@0: new InputPort({ id: "frame-mailbox" }), michael@0: new InputPort({ id: "frame-change" }), michael@0: new InputPort({ id: "frame-changed" }) michael@0: ])); michael@0: exports.Frames = Frames;