view.js 4.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144
  1. /* This Source Code Form is subject to the terms of the Mozilla Public
  2. * License, v. 2.0. If a copy of the MPL was not distributed with this
  3. * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
  4. "use strict";
  5. module.metadata = {
  6. "stability": "experimental",
  7. "engines": {
  8. "Firefox": "> 28"
  9. }
  10. };
  11. const { Cu, Ci } = require("chrome");
  12. const { CustomizableUI } = Cu.import('resource:///modules/CustomizableUI.jsm', {});
  13. const { subscribe, send, Reactor, foldp, lift, merges, keepIf } = require("../../event/utils");
  14. const { InputPort } = require("../../input/system");
  15. const { OutputPort } = require("../../output/system");
  16. const { LastClosed } = require("../../input/browser");
  17. const { pairs, keys, object, each } = require("../../util/sequence");
  18. const { curry, compose } = require("../../lang/functional");
  19. const { getFrameElement, getOuterId,
  20. getByOuterId, getOwnerBrowserWindow } = require("../../window/utils");
  21. const { patch, diff } = require("diffpatcher/index");
  22. const { encode } = require("../../base64");
  23. const { Frames } = require("../../input/frame");
  24. const XUL_NS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
  25. const HTML_NS = "http://www.w3.org/1999/xhtml";
  26. const OUTER_FRAME_URI = module.uri.replace(/\.js$/, ".html");
  27. const mailbox = new OutputPort({ id: "frame-mailbox" });
  28. const frameID = frame => frame.id.replace("outer-", "");
  29. const windowID = compose(getOuterId, getOwnerBrowserWindow);
  30. const getOuterFrame = (windowID, frameID) =>
  31. getByOuterId(windowID).document.getElementById("outer-" + frameID);
  32. const listener = ({target, source, data, origin, timeStamp}) => {
  33. // And sent received message to outbox so that frame API model
  34. // will deal with it.
  35. if (source && source !== target) {
  36. const frame = getFrameElement(target);
  37. const id = frameID(frame);
  38. send(mailbox, object([id, {
  39. outbox: {type: "message",
  40. source: {id: id, ownerID: windowID(frame)},
  41. data: data,
  42. origin: origin,
  43. timeStamp: timeStamp}}]));
  44. }
  45. };
  46. // Utility function used to create frame with a given `state` and
  47. // inject it into given `window`.
  48. const registerFrame = ({id, url}) => {
  49. CustomizableUI.createWidget({
  50. id: id,
  51. type: "custom",
  52. removable: true,
  53. onBuild: document => {
  54. let view = document.createElementNS(XUL_NS, "toolbaritem");
  55. view.setAttribute("id", id);
  56. view.setAttribute("flex", 2);
  57. let innerFrame = document.createElementNS(HTML_NS, "iframe");
  58. innerFrame.setAttribute("id", id);
  59. innerFrame.setAttribute("src", url);
  60. innerFrame.setAttribute("seamless", "seamless");
  61. innerFrame.setAttribute("sandbox", "allow-scripts");
  62. innerFrame.setAttribute("scrolling", "no");
  63. innerFrame.setAttribute("data-is-sdk-inner-frame", true);
  64. innerFrame.setAttribute("style", [ "border:none",
  65. "position:absolute", "width:100%", "top: 0",
  66. "left: 0", "overflow: hidden"].join(";"));
  67. let outerFrame = document.createElementNS(XUL_NS, "iframe");
  68. outerFrame.setAttribute("src", OUTER_FRAME_URI + "#" +
  69. encode(innerFrame.outerHTML));
  70. outerFrame.setAttribute("id", "outer-" + id);
  71. outerFrame.setAttribute("data-is-sdk-outer-frame", true);
  72. outerFrame.setAttribute("type", "content");
  73. outerFrame.setAttribute("transparent", true);
  74. outerFrame.setAttribute("flex", 2);
  75. outerFrame.setAttribute("style", "overflow: hidden;");
  76. outerFrame.setAttribute("scrolling", "no");
  77. outerFrame.setAttribute("disablehistory", true);
  78. outerFrame.setAttribute("seamless", "seamless");
  79. view.appendChild(outerFrame);
  80. return view;
  81. }
  82. });
  83. };
  84. const unregisterFrame = CustomizableUI.destroyWidget;
  85. const deliverMessage = curry((frameID, data, windowID) => {
  86. const frame = getOuterFrame(windowID, frameID);
  87. const content = frame && frame.contentWindow;
  88. if (content)
  89. content.postMessage(data, content.location.origin);
  90. });
  91. const updateFrame = (id, {inbox, owners}, present) => {
  92. if (inbox) {
  93. const { data, target:{ownerID}, source } = present[id].inbox;
  94. if (ownerID)
  95. deliverMessage(id, data, ownerID);
  96. else
  97. each(deliverMessage(id, data), keys(present[id].owners));
  98. }
  99. each(setupView(id), pairs(owners));
  100. };
  101. const setupView = curry((frameID, [windowID, state]) => {
  102. if (state && state.readyState === "loading") {
  103. const frame = getOuterFrame(windowID, frameID);
  104. // Setup a message listener on contentWindow.
  105. frame.contentWindow.addEventListener("message", listener);
  106. }
  107. });
  108. const reactor = new Reactor({
  109. onStep: (present, past) => {
  110. const delta = diff(past, present);
  111. // Apply frame changes
  112. each(([id, update]) => {
  113. if (update === null)
  114. unregisterFrame(id);
  115. else if (past[id])
  116. updateFrame(id, update, present);
  117. else
  118. registerFrame(update);
  119. }, pairs(delta));
  120. },
  121. onEnd: state => each(unregisterFrame, keys(state))
  122. });
  123. reactor.run(Frames);