<commit_before>/*
 * Copyright (c) 2014, Ericsson AB. All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without modification,
 * are permitted provided that the following conditions are met:
 *
 * 1. Redistributions of source code must retain the above copyright notice, this
 * list of conditions and the following disclaimer.
 *
 * 2. Redistributions in binary form must reproduce the above copyright notice, this
 * list of conditions and the following disclaimer in the documentation and/or other
 * materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
 * IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
 * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
 * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
 * OF SUCH DAMAGE.
 */

if (window.document && window.document.documentElement) {
   var owrtag = document.createElement("script");
   document.documentElement.appendChild(owrtag);
   owrtag.src = "http://localhost:10717/owr.js";
}
<commit_msg>safari: Get script via XHR<commit_after>/*
 * Copyright (c) 2014, Ericsson AB. All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without modification,
 * are permitted provided that the following conditions are met:
 *
 * 1. Redistributions of source code must retain the above copyright notice, this
 * list of conditions and the following disclaimer.
 *
 * 2. Redistributions in binary form must reproduce the above copyright notice, this
 * list of conditions and the following disclaimer in the documentation and/or other
 * materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
 * IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
 * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
 * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
 * OF SUCH DAMAGE.
 */

try {
    var xhr = new XMLHttpRequest();
    xhr.open("GET", "http://localhost:10717/owr.js", false);
    xhr.send();
    var scriptElement = document.documentElement.appendChild(document.createElement("script"));
    scriptElement.src = "data:text/javascript;base64," + btoa(xhr.responseText);
} catch (e) {}
<|endoftext|>


<commit_before>module.exports = function (wallaby) {
  return {
    files: [
      {pattern: 'src/**/*.js'}
    ],

    tests: [
      {pattern: 'test/**/*.js'}
    ],
    testFramework: 'ava',
    env: {
      type: 'node',
      runner: 'node'
    },
    compilers: {
      '**/*.js': wallaby.compilers.babel()
    },
    debug: true
  };
};<commit_msg>Disable wallaby debug<commit_after>module.exports = function (wallaby) {
  return {
    files: [
      {pattern: 'src/**/*.js'}
    ],

    tests: [
      {pattern: 'test/**/*.js'}
    ],
    testFramework: 'ava',
    env: {
      type: 'node',
      runner: 'node'
    },
    compilers: {
      '**/*.js': wallaby.compilers.babel()
    },
    debug: false
  }
}<|endoftext|>


<commit_before>/* See license.txt for terms of usage */

"use strict";

module.metadata = {
  "stability": "stable"
};

const self = require("sdk/self");
const options = require("@loader/options");

const { Cu, Ci, Cc } = require("chrome");
const { Trace, TraceError } = require("firebug.sdk/lib/core/trace.js").get(module.id);
const { Class } = require("sdk/core/heritage");
const { getMostRecentBrowserWindow } = require("sdk/window/utils");
const { Content } = require("firebug.sdk/lib/core/content.js");
const { on, off, emit } = require("sdk/event/core");
const DomEvents = require("sdk/dom/events");
const { PixelPerfectStore } = require("./pixel-perfect-store");
const { Locale } = require("firebug.sdk/lib/core/locale.js");
const { openTab } = require("sdk/tabs/utils");
const { defer, resolve, all } = require("sdk/core/promise");
const { PixelPerfectFront } = require("./pixel-perfect-front.js");
const { Rdp } = require("firebug.sdk/lib/core/rdp.js");
const { Xul } = require("firebug.sdk/lib/core/xul.js");
const { Win } = require("firebug.sdk/lib/core/window.js");

// DevTools
const { devtools } = Cu.import("resource://gre/modules/devtools/Loader.jsm", {});
const { makeInfallible } = devtools["require"]("devtools/toolkit/DevToolsUtils.js");

// Platform Services
const ioService = Cc["@mozilla.org/network/io-service;1"].getService(Ci.nsIIOService);

// Constants
const STYLESHEET = "chrome://pixelperfect/skin/ua.css";
const XUL_NS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";

// URL of the {@InspectorActor} module. This module will be
// installed and loaded on the backend.
const actorModuleUrl = options.prefixURI + "lib/pixel-perfect-actor.js";

// Shortcuts
const { VBOX, HBOX, RESIZER, PANEL, IFRAME, STACK, SPACER } = Xul;

/**
 * This object represents the main Pixel Perfect UI. It's implemented
 * as a popup panel that allows creating new layers and modify their
 * properties. Content of the panel is rendered inside an <iframe>
 * with content that has limited privileges (no chrome access).
 *
 * The content frame itself uses ReactJS to render HTML UI and other
 * standard web technologies such as RequireJS for loading JS modules.
 * The communication between the frame and this object is done using
 * JSON messages sent through associated message manager.
 *
 * Summary of responsibilities:
 * 1. Create a popup panel with inner frame.
 * 2. Listening for messages coming from the frame (user actions)
 * 3. Sending messages the frame (update).
 * 4. Registering back-end actor and attach/detach to/from it.
 * 5. Listening to events coming from the actor (e.g. drag and drop events).
 * 6. Listening to events coming from the storage (to update the UI)
 */
const PixelPerfectPopup = Class(
/** @lends PixelPerfectPopup */
{
  // Initialization

  initialize: function(toolbox) {
    this.toolbox = toolbox;

    this.onPanelReady = this.onPanelReady.bind(this);
    this.onMessage = this.onMessage.bind(this);

    // Create data store.
    // xxxHonza: the data store instance should be shared across contexts
    this.store = new PixelPerfectStore();

    this.onAddLayer = this.onAddLayer.bind(this);
    this.onRemoveLayer = this.onRemoveLayer.bind(this);
    this.onModifyLayer = this.onModifyLayer.bind(this);
    this.onMoveLayer = this.onMoveLayer.bind(this);

    // Listen to data store changes.
    on(this.store, "add", this.onAddLayer);
    on(this.store, "remove", this.onRemoveLayer);
    on(this.store, "modify", this.onModifyLayer);
    on(this.store, "move", this.onMoveLayer);

    // Panel event handlers
    this.onPopupHidden = this.onPopupHidden.bind(this);
    this.onPopupShown = this.onPopupShown.bind(this);

    // Actor drag & drop events
    this.onDragStart = this.onDragStart.bind(this);
    this.onDrag = this.onDrag.bind(this);
    this.onDragEnd = this.onDragEnd.bind(this);
  },

  destroy: function() {
    if (!this.panel) {
      return resolve();
    }

    off(this.store, "add", this.onAddLayer);
    off(this.store, "remove", this.onRemoveLayer);
    off(this.store, "modify", this.onModifyLayer);
    off(this.store, "move", this.onMoveLayer);

    this.store.destroy();
    this.panel.remove();
    this.panel = null;

    // Detach is asynchronous (involves RDP), so returns a promise.
    return this.detach();
  },

  // Visibility

  toggle: function() {
    if (this.isOpen()) {
      return this.hide();
    } else {
      return this.show();
    }
  },

  isOpen: function() {
    return (this.panel && this.panel.state == "open");
  },

  show: function() {
    let browser = getMostRecentBrowserWindow();
    let doc = browser.document;
    let button = doc.getElementById("pixel-perfect-start-button");

    // Create panel with content iframe that implements the entire UI.
    // The iframe uses type='content' and so, it's content has limited
    // (content) privileges. The communication with the content is done
    // through message managers.
    if (!this.panel) {
      this.createPanel();
    }

    // For debugging purposes. Reload the frame so, changes are
    // applied immediately, no browser restart needed.
    // if (this.panelFrame && this.panelFrame.contentDocument) {
    //   this.panelFrame.contentDocument.location.reload();
    // }

    if (this.position) {
      this.panel.openPopupAtScreen(this.position.x, this.position.y);
    } else {
      this.panel.sizeTo(400, 230);
      this.panel.openPopup(button, "after_start");
    }

    Trace.sysout("PixelPerfectPopup.show;", this.panel);

    return this.attach();
  },

  hide: function() {
    if (!this.panel) {
      return resolve();
    }

    this.panel.hidePopup();

    // Detach from the backend
    return this.detach();
  },

  // Popup Panel Content

  createPanel: function() {
    let browser = getMostRecentBrowserWindow();
    let doc = browser.document;

    // Definition of the new panel content layout. Part of the layout
    // is also a 'resizer' element that is located at the bottom right
    // corner. The resizer is used to change size of the popup panel.
    // Note that using type='window' for RESIZER breaks the resizing
    // on OS X (resizer not available).
    let panel =
      PANEL({"id": "pixel-perfect-panel",
        "noautohide": "true", "titlebar": "normal", "close": "true",
        "noautofocus": "true", "label": Locale.$STR("pixelPerfect.title")},
        STACK({"flex": "1"},
          IFRAME({"type": "content", "border": "0", "flex": "1",
            "src": self.data.url("./popup.html")}),
          VBOX({"flex": "1", "style": "pointer-events: none;"},
            SPACER({"flex": "1"}),
            HBOX({"style": "max-height: 16px;"},
              SPACER({"flex": "1", "style": "background-color: transparent"}),
              RESIZER({"dir": "bottomend", /*"type": "window",*/
                "style": "background: url('" +
                "chrome://pixelperfect/skin/resizer.png') no-repeat; " +
                "pointer-events: auto;"})
            )
          )
        )
      );

    // Build the panel and its content.
    let container = doc.getElementById("mainPopupSet");
    this.panel = panel.build(container);
    this.panelFrame = this.panel.querySelector("iframe");

    this.panel.style.border = "0";

    DomEvents.on(this.panel, "popuphidden", this.onPopupHidden);
    DomEvents.on(this.panel, "popupshown", this.onPopupShown);

    // Load content script and handle messages sent from it.
    let { messageManager } = this.panelFrame.frameLoader;
    if (messageManager) {
      let url = self.data.url("popup-frame-script.js");
      messageManager.loadFrameScript(url, false);
      messageManager.addMessageListener("message", this.onMessage);
      messageManager.addMessageListener("sdk/event/ready", this.onPanelReady);
    }

    return this.panel;
  },

  onPanelReady: makeInfallible(function() {
    Trace.sysout("PixelPerfectPopup.onPanelReady;", arguments);

    let win = this.panelFrame.contentWindow;
    let ContentTrace = {
      sysout: () => FBTrace.sysout.apply(FBTrace, arguments)
    }

    Content.exportIntoContentScope(win, ContentTrace, "Trace");
    Content.exportIntoContentScope(win, Locale, "Locale");
  }),

  // Popup Panel Event Handlers

  onPopupShown: function(event) {
    Trace.sysout("PixelPerfectPopup.onPanelShown;", arguments);

    emit(this, "popupshown");
  },

  onPopupHidden: function(event) {
    Trace.sysout("PixelPerfectPopup.onPanelHidden;", this.panel);

    // Store position of the panel on the screen.
    this.position = {
      x: this.panel.boxObject.screenX,
      y: this.panel.boxObject.screenY
    };

    emit(this, "popuphidden");
  },

  // Communication: content <-> chrome

  /**
   * Handle messages coming from the content scope (from panel's iframe).
   */
  onMessage: function(msg) {
    Trace.sysout("PixelPerfectPopup.onMessage; (from panel content)", msg);

    let result;
    let event = msg.data;

    switch (event.type) {
    case "panel-refreshed":
      // The panel has been refreshed, just emit the event further
      // to other listeners.
      emit(this, "panel-refreshed");
      return;

    case "panel-ready":
      // Just send back initial 'refresh' message.
      break;

    case "add":
    case "remove":
    case "modify":
    case "move":
      // Execute specified method.
      result = this.store[event.type].apply(this.store, event.args);
      break;

    case "open-homepage":
      // Open a new tab with the home page.
      // Refresh isn't needed, so bail out.
      Win.openNewTab(options.manifest.homepage);
      return;
    }

    // Refresh panel UI (frame content).
    this.refreshPopupContent();

    return result;
  },

  /**
   * Send message to the content scope (panel's iframe)
   */
  postContentMessage: function(id, data) {
    let { messageManager } = this.panelFrame.frameLoader;
    messageManager.sendAsyncMessage("pixelperfect/event/message", {
      type: id,
      bubbles: false,
      cancelable: false,
      data: data,
      origin: this.url,
    });
  },

  refreshPopupContent: function() {
    let message = {
      version: self.version,
      layers: this.store.layers
    };

    // Make sure the panel content is refreshed.
    this.postContentMessage("refresh", JSON.stringify(message));
  },

  // Backend

  attach: function() {
    if (this.front) {
      return resolve(this.front);
    }

    // Inspector actor registration options.
    let config = {
      prefix: PixelPerfectFront.prototype.typeName,
      actorClass: "PixelPerfectActor",
      frontClass: PixelPerfectFront,
      moduleUrl: actorModuleUrl
    };

    let deferred = defer();
    let client = this.toolbox.target.client;

    // Register as tab actor.
    let tab = Rdp.registerTabActor(client, config).
      then(({registrar, front}) => {
        Trace.sysout("PixelPerfectPopup.attach; READY", front);

        this.front = front;

        // xxxHonza: unregister actor on shutdown/disable/uninstall
        // but not on toolbox close.
        this.registrar = registrar;

        // Drag-drop listener (events sent from the backend)
        on(this.front, "dragstart", this.onDragStart);
        on(this.front, "drag", this.onDrag);
        on(this.front, "dragend", this.onDragEnd);

        // Load user agent stylesheet for anonymous content.
        let source = getResource(STYLESHEET);
        this.front.loadSheet(source);

        let promises = [];

        // Send layers to the backend. Image URLs are converted into
        // data URIs and sent through the RDP as text data. Note that
        // the backend can be a remote device where local file:// URLs
        // wouldn't work.
        this.store.forEachLayer(layer => {
          promises.push(this.onAddLayer(layer));
        });

        // Wait till all layers are properly loaded on the backend.
        all(promises).then(() => {
          emit(this, "attached", front);
          deferred.resolve(front);
        });
    });

    return deferred.promise;
  },

  detach: function() {
    if (!this.front) {
      return resolve();
    }

    off(this.front, "dragstart", this.onDragStart);
    off(this.front, "drag", this.onDrag);
    off(this.front, "dragend", this.onDragEnd);

    let deferred = defer();
    this.front.detach().then(response => {
      Trace.sysout("PixelPerfectPopup.detach; DONE", response);

      emit(this, "detached", response);
      deferred.resolve(response);
    });

    this.front = null;

    return deferred.promise;
  },

  // Actor drag & drop events

  onDragStart: function(data) {
    let message = { selection: data.id };
    this.postContentMessage("refresh", JSON.stringify(message));
  },

  onDrag: function(data) {
    this.doNotUpdateBackend = true;

    try {
      // Update the store and panel content (do not update the backend).
      this.store.modify(data.id, {x: data.x, y: data.y});
      let message = { layers: this.store.layers };
      this.postContentMessage("refresh", JSON.stringify(message));
    } catch (err) {
      TraceError.sysout("PixelPerfect.onDrag; ERROR " + err, err);
    }

    this.doNotUpdateBackend = false;
  },

  onDragEnd: function(data) {
    let message = { layers: this.store.layers };
    this.postContentMessage("refresh", JSON.stringify(message));
  },

  // Layer Store Handlers

  onAddLayer: function(layer) {
    let deferred = defer();

    this.store.getImageDataURL(layer.id).then(result => {
      layer = this.store.cloneLayer(layer);
      layer.url = result.data;
      this.front.addLayer(layer).then(response => {
        emit(this, "layer-added", response);
        deferred.resolve(layer);
      });
    });

    return deferred.promise;
  },

  onRemoveLayer: function(id) {
    return this.front.removeLayer(id).then(response => {
      emit(this, "layer-removed", response);
    });
  },

  onModifyLayer: function(id, props) {
    if (this.doNotUpdateBackend) {
      return resolve();
    }

    return this.front.modifyLayer(id, props).then(response => {
      emit(this, "layer-modified", response);
    });
  },

  onMoveLayer: function(from, to) {
    return this.front.moveLayer(from, to).then(response => {
      emit(this, "layer-moved", response);
    });
  },
});

// Helpers

function getResource(aURL) {
  try {
    let channel = ioService.newChannel(aURL, null, null);
    let input = channel.open();
    return readFromStream(input);
  }
  catch (e) {
  }
}

function readFromStream(stream, charset) {
  let sis = Cc["@mozilla.org/scriptableinputstream;1"].
    createInstance(Ci.nsIScriptableInputStream);
  sis.init(stream);

  let segments = [];
  for (let count = stream.available(); count; count = stream.available()) {
    segments.push(sis.readBytes(count));
  }

  sis.close();

  return segments.join("");
};

// Exports from this module
exports.PixelPerfectPopup = PixelPerfectPopup;
<commit_msg>Detach from the backend if the popup panel is hidden.<commit_after>/* See license.txt for terms of usage */

"use strict";

module.metadata = {
  "stability": "stable"
};

const self = require("sdk/self");
const options = require("@loader/options");

const { Cu, Ci, Cc } = require("chrome");
const { Trace, TraceError } = require("firebug.sdk/lib/core/trace.js").get(module.id);
const { Class } = require("sdk/core/heritage");
const { getMostRecentBrowserWindow } = require("sdk/window/utils");
const { Content } = require("firebug.sdk/lib/core/content.js");
const { on, off, emit } = require("sdk/event/core");
const DomEvents = require("sdk/dom/events");
const { PixelPerfectStore } = require("./pixel-perfect-store");
const { Locale } = require("firebug.sdk/lib/core/locale.js");
const { openTab } = require("sdk/tabs/utils");
const { defer, resolve, all } = require("sdk/core/promise");
const { PixelPerfectFront } = require("./pixel-perfect-front.js");
const { Rdp } = require("firebug.sdk/lib/core/rdp.js");
const { Xul } = require("firebug.sdk/lib/core/xul.js");
const { Win } = require("firebug.sdk/lib/core/window.js");

// DevTools
const { devtools } = Cu.import("resource://gre/modules/devtools/Loader.jsm", {});
const { makeInfallible } = devtools["require"]("devtools/toolkit/DevToolsUtils.js");

// Platform Services
const ioService = Cc["@mozilla.org/network/io-service;1"].getService(Ci.nsIIOService);

// Constants
const STYLESHEET = "chrome://pixelperfect/skin/ua.css";
const XUL_NS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";

// URL of the {@InspectorActor} module. This module will be
// installed and loaded on the backend.
const actorModuleUrl = options.prefixURI + "lib/pixel-perfect-actor.js";

// Shortcuts
const { VBOX, HBOX, RESIZER, PANEL, IFRAME, STACK, SPACER } = Xul;

/**
 * This object represents the main Pixel Perfect UI. It's implemented
 * as a popup panel that allows creating new layers and modify their
 * properties. Content of the panel is rendered inside an <iframe>
 * with content that has limited privileges (no chrome access).
 *
 * The content frame itself uses ReactJS to render HTML UI and other
 * standard web technologies such as RequireJS for loading JS modules.
 * The communication between the frame and this object is done using
 * JSON messages sent through associated message manager.
 *
 * Summary of responsibilities:
 * 1. Create a popup panel with inner frame.
 * 2. Listening for messages coming from the frame (user actions)
 * 3. Sending messages the frame (update).
 * 4. Registering back-end actor and attach/detach to/from it.
 * 5. Listening to events coming from the actor (e.g. drag and drop events).
 * 6. Listening to events coming from the storage (to update the UI)
 */
const PixelPerfectPopup = Class(
/** @lends PixelPerfectPopup */
{
  // Initialization

  initialize: function(toolbox) {
    this.toolbox = toolbox;

    this.onPanelReady = this.onPanelReady.bind(this);
    this.onMessage = this.onMessage.bind(this);

    // Create data store.
    // xxxHonza: the data store instance should be shared across contexts
    this.store = new PixelPerfectStore();

    this.onAddLayer = this.onAddLayer.bind(this);
    this.onRemoveLayer = this.onRemoveLayer.bind(this);
    this.onModifyLayer = this.onModifyLayer.bind(this);
    this.onMoveLayer = this.onMoveLayer.bind(this);

    // Listen to data store changes.
    on(this.store, "add", this.onAddLayer);
    on(this.store, "remove", this.onRemoveLayer);
    on(this.store, "modify", this.onModifyLayer);
    on(this.store, "move", this.onMoveLayer);

    // Panel event handlers
    this.onPopupHidden = this.onPopupHidden.bind(this);
    this.onPopupShown = this.onPopupShown.bind(this);

    // Actor drag & drop events
    this.onDragStart = this.onDragStart.bind(this);
    this.onDrag = this.onDrag.bind(this);
    this.onDragEnd = this.onDragEnd.bind(this);
  },

  destroy: function() {
    if (!this.panel) {
      return resolve();
    }

    off(this.store, "add", this.onAddLayer);
    off(this.store, "remove", this.onRemoveLayer);
    off(this.store, "modify", this.onModifyLayer);
    off(this.store, "move", this.onMoveLayer);

    this.store.destroy();
    this.panel.remove();
    this.panel = null;

    // Detach is asynchronous (involves RDP), so returns a promise.
    return this.detach();
  },

  // Visibility

  toggle: function() {
    if (this.isOpen()) {
      return this.hide();
    } else {
      return this.show();
    }
  },

  isOpen: function() {
    return (this.panel && this.panel.state == "open");
  },

  show: function() {
    let browser = getMostRecentBrowserWindow();
    let doc = browser.document;
    let button = doc.getElementById("pixel-perfect-start-button");

    // Create panel with content iframe that implements the entire UI.
    // The iframe uses type='content' and so, it's content has limited
    // (content) privileges. The communication with the content is done
    // through message managers.
    if (!this.panel) {
      this.createPanel();
    }

    // For debugging purposes. Reload the frame so, changes are
    // applied immediately, no browser restart needed.
    // if (this.panelFrame && this.panelFrame.contentDocument) {
    //   this.panelFrame.contentDocument.location.reload();
    // }

    if (this.position) {
      this.panel.openPopupAtScreen(this.position.x, this.position.y);
    } else {
      this.panel.sizeTo(400, 230);
      this.panel.openPopup(button, "after_start");
    }

    Trace.sysout("PixelPerfectPopup.show;", this.panel);

    return this.attach();
  },

  hide: function() {
    if (!this.panel) {
      return resolve();
    }

    this.panel.hidePopup();

    // Detach from the backend
    return this.detach();
  },

  // Popup Panel Content

  createPanel: function() {
    let browser = getMostRecentBrowserWindow();
    let doc = browser.document;

    // Definition of the new panel content layout. Part of the layout
    // is also a 'resizer' element that is located at the bottom right
    // corner. The resizer is used to change size of the popup panel.
    // Note that using type='window' for RESIZER breaks the resizing
    // on OS X (resizer not available).
    let panel =
      PANEL({"id": "pixel-perfect-panel",
        "noautohide": "true", "titlebar": "normal", "close": "true",
        "noautofocus": "true", "label": Locale.$STR("pixelPerfect.title")},
        STACK({"flex": "1"},
          IFRAME({"type": "content", "border": "0", "flex": "1",
            "src": self.data.url("./popup.html")}),
          VBOX({"flex": "1", "style": "pointer-events: none;"},
            SPACER({"flex": "1"}),
            HBOX({"style": "max-height: 16px;"},
              SPACER({"flex": "1", "style": "background-color: transparent"}),
              RESIZER({"dir": "bottomend", /*"type": "window",*/
                "style": "background: url('" +
                "chrome://pixelperfect/skin/resizer.png') no-repeat; " +
                "pointer-events: auto;"})
            )
          )
        )
      );

    // Build the panel and its content.
    let container = doc.getElementById("mainPopupSet");
    this.panel = panel.build(container);
    this.panelFrame = this.panel.querySelector("iframe");

    this.panel.style.border = "0";

    DomEvents.on(this.panel, "popuphidden", this.onPopupHidden);
    DomEvents.on(this.panel, "popupshown", this.onPopupShown);

    // Load content script and handle messages sent from it.
    let { messageManager } = this.panelFrame.frameLoader;
    if (messageManager) {
      let url = self.data.url("popup-frame-script.js");
      messageManager.loadFrameScript(url, false);
      messageManager.addMessageListener("message", this.onMessage);
      messageManager.addMessageListener("sdk/event/ready", this.onPanelReady);
    }

    return this.panel;
  },

  onPanelReady: makeInfallible(function() {
    Trace.sysout("PixelPerfectPopup.onPanelReady;", arguments);

    let win = this.panelFrame.contentWindow;
    let ContentTrace = {
      sysout: () => FBTrace.sysout.apply(FBTrace, arguments)
    }

    Content.exportIntoContentScope(win, ContentTrace, "Trace");
    Content.exportIntoContentScope(win, Locale, "Locale");
  }),

  // Popup Panel Event Handlers

  onPopupShown: function(event) {
    Trace.sysout("PixelPerfectPopup.onPanelShown;", arguments);

    emit(this, "popupshown");
  },

  onPopupHidden: function(event) {
    Trace.sysout("PixelPerfectPopup.onPanelHidden;", this.panel);

    // Store position of the panel on the screen.
    this.position = {
      x: this.panel.boxObject.screenX,
      y: this.panel.boxObject.screenY
    };

    // Detach from the backend if the popup panel is hidden.
    this.detach();

    emit(this, "popuphidden");
  },

  // Communication: content <-> chrome

  /**
   * Handle messages coming from the content scope (from panel's iframe).
   */
  onMessage: function(msg) {
    Trace.sysout("PixelPerfectPopup.onMessage; (from panel content)", msg);

    let result;
    let event = msg.data;

    switch (event.type) {
    case "panel-refreshed":
      // The panel has been refreshed, just emit the event further
      // to other listeners.
      emit(this, "panel-refreshed");
      return;

    case "panel-ready":
      // Just send back initial 'refresh' message.
      break;

    case "add":
    case "remove":
    case "modify":
    case "move":
      // Execute specified method.
      result = this.store[event.type].apply(this.store, event.args);
      break;

    case "open-homepage":
      // Open a new tab with the home page.
      // Refresh isn't needed, so bail out.
      Win.openNewTab(options.manifest.homepage);
      return;
    }

    // Refresh panel UI (frame content).
    this.refreshPopupContent();

    return result;
  },

  /**
   * Send message to the content scope (panel's iframe)
   */
  postContentMessage: function(id, data) {
    let { messageManager } = this.panelFrame.frameLoader;
    messageManager.sendAsyncMessage("pixelperfect/event/message", {
      type: id,
      bubbles: false,
      cancelable: false,
      data: data,
      origin: this.url,
    });
  },

  refreshPopupContent: function() {
    let message = {
      version: self.version,
      layers: this.store.layers
    };

    // Make sure the panel content is refreshed.
    this.postContentMessage("refresh", JSON.stringify(message));
  },

  // Backend

  attach: function() {
    if (this.front) {
      return resolve(this.front);
    }

    // Inspector actor registration options.
    let config = {
      prefix: PixelPerfectFront.prototype.typeName,
      actorClass: "PixelPerfectActor",
      frontClass: PixelPerfectFront,
      moduleUrl: actorModuleUrl
    };

    let deferred = defer();
    let client = this.toolbox.target.client;

    // Register as tab actor.
    let tab = Rdp.registerTabActor(client, config).
      then(({registrar, front}) => {
        Trace.sysout("PixelPerfectPopup.attach; READY", front);

        this.front = front;

        // xxxHonza: unregister actor on shutdown/disable/uninstall
        // but not on toolbox close.
        this.registrar = registrar;

        // Drag-drop listener (events sent from the backend)
        on(this.front, "dragstart", this.onDragStart);
        on(this.front, "drag", this.onDrag);
        on(this.front, "dragend", this.onDragEnd);

        // Load user agent stylesheet for anonymous content.
        let source = getResource(STYLESHEET);
        this.front.loadSheet(source);

        let promises = [];

        // Send layers to the backend. Image URLs are converted into
        // data URIs and sent through the RDP as text data. Note that
        // the backend can be a remote device where local file:// URLs
        // wouldn't work.
        this.store.forEachLayer(layer => {
          promises.push(this.onAddLayer(layer));
        });

        // Wait till all layers are properly loaded on the backend.
        all(promises).then(() => {
          emit(this, "attached", front);
          deferred.resolve(front);
        });
    });

    return deferred.promise;
  },

  detach: function() {
    if (!this.front) {
      return resolve();
    }

    off(this.front, "dragstart", this.onDragStart);
    off(this.front, "drag", this.onDrag);
    off(this.front, "dragend", this.onDragEnd);

    let deferred = defer();
    this.front.detach().then(response => {
      Trace.sysout("PixelPerfectPopup.detach; DONE", response);

      emit(this, "detached", response);
      deferred.resolve(response);
    });

    this.front = null;

    return deferred.promise;
  },

  // Actor drag & drop events

  onDragStart: function(data) {
    let message = { selection: data.id };
    this.postContentMessage("refresh", JSON.stringify(message));
  },

  onDrag: function(data) {
    this.doNotUpdateBackend = true;

    try {
      // Update the store and panel content (do not update the backend).
      this.store.modify(data.id, {x: data.x, y: data.y});
      let message = { layers: this.store.layers };
      this.postContentMessage("refresh", JSON.stringify(message));
    } catch (err) {
      TraceError.sysout("PixelPerfect.onDrag; ERROR " + err, err);
    }

    this.doNotUpdateBackend = false;
  },

  onDragEnd: function(data) {
    let message = { layers: this.store.layers };
    this.postContentMessage("refresh", JSON.stringify(message));
  },

  // Layer Store Handlers

  onAddLayer: function(layer) {
    let deferred = defer();

    this.store.getImageDataURL(layer.id).then(result => {
      layer = this.store.cloneLayer(layer);
      layer.url = result.data;
      this.front.addLayer(layer).then(response => {
        emit(this, "layer-added", response);
        deferred.resolve(layer);
      });
    });

    return deferred.promise;
  },

  onRemoveLayer: function(id) {
    return this.front.removeLayer(id).then(response => {
      emit(this, "layer-removed", response);
    });
  },

  onModifyLayer: function(id, props) {
    if (this.doNotUpdateBackend) {
      return resolve();
    }

    return this.front.modifyLayer(id, props).then(response => {
      emit(this, "layer-modified", response);
    });
  },

  onMoveLayer: function(from, to) {
    return this.front.moveLayer(from, to).then(response => {
      emit(this, "layer-moved", response);
    });
  },
});

// Helpers

function getResource(aURL) {
  try {
    let channel = ioService.newChannel(aURL, null, null);
    let input = channel.open();
    return readFromStream(input);
  }
  catch (e) {
  }
}

function readFromStream(stream, charset) {
  let sis = Cc["@mozilla.org/scriptableinputstream;1"].
    createInstance(Ci.nsIScriptableInputStream);
  sis.init(stream);

  let segments = [];
  for (let count = stream.available(); count; count = stream.available()) {
    segments.push(sis.readBytes(count));
  }

  sis.close();

  return segments.join("");
};

// Exports from this module
exports.PixelPerfectPopup = PixelPerfectPopup;
<|endoftext|>


<commit_before>/**
 * Interpreter of evo language. Main goal of this interpreter is to run the
 * script as fast as possible. It interprets code line by line from up to the
 * bottom. This module doesn't have any checks (maybe only minimal) to increase
 * running speed. In general script looks like simple assembler and has binary
 * format stored in UInt16Array. Every line of code is _LINE_SEGMENTS
 * words (two bytes) in this array. So, for evo interpreter - script is a
 * binary array of UInt16 words. Take a look at simple script
 *
 *     0000 0000 0001 0000       # set  1    zero
 *     0000 0001 0000 0001       # move zero one
 *     0000 0002 0001            # inc  one
 *
 * One standard script line format is:
 *
 *     label cmd   arg1  arg2    # segments description
 *     0000  0001  0002  0003    # binary representation
 *  or      move  two   three   # readable representation
 *  or [0,   1,    2,    3]      # JS representation
 * where:
 *     label - Unique label number. 0000 means no label
 *     cmd   - Command, like move, add, inc, jump and so on
 *     arg1  - First argument. In most case the index of variable
 *     arg2  - Second argument. In most case the index of variable
 *
 * Previous example shows how script:
 *
 *     move two three
 *
 * is presented inside the interpreter. First 0000 means no label. Value greater
 * then 0000 means unique label number. Second 0001 means command index. For this
 * example is 'move' command. Two arguments 0002 and 0003 means two variables, with
 * appropriate indexes. So this line of code will move the value from variable
 * with index 0002 to variable with index 0003.
 *
 * Every command handler works in a similar way. It obtains only one parameter -
 * index of current command line in binary array. Using this index, current handler
 * may obtain all code line related info like: label, command and all arguments:
 *
 *     _code[i + 0] label
 *     _code[i + 1] cmd
 *     _code[i + 2] arg1
 *     _code[i + 3] arg2
 *
 * For example:
 *
 *     function _inc(i) {
 *         _vars[_code[i + 2]]++;
 *     }
 *
 * You should also note, that evo language should be read from left to the right.
 * For example command:
 *
 *     move two three
 *
 * Should be read as 'Move value of "two" variable into "three" variable' and not
 * like 'Move value of "three" variable into "two" variable'.
 *
 * Dependencies:
 *     Core
 *
 * Example:
 *     //
 *     // Binary representation of our two
 *     // lines script (4 words in each line)
 *     //
 *     var code = new Uint16Array(2 * 4);
 *
 *     // set 7 zero
 *     code[0]  = 0;  // no label
 *     code[1]  = 0;  // set
 *     code[2]  = 7;  // 7
 *     code[3]  = 0;  // zero var
 *
 *     // move zero one
 *     code[4]  = 0;  // no label
 *     code[5]  = 1;  // move
 *     code[6]  = 0;  // zero var
 *     code[7]  = 1;  // one var
 *
 *     Evo.Interpreter.run(code);
 *
 * @author DeadbraiN
 */
Evo.Interpreter = (function () {
    /**
     * @constant
     * {Number} Amount of segments (parts) in one script line: label, keyword, arg1, arg2
     */
    var _LINE_SEGMENTS = 4;
    /**
     * {UInt16Array} Array of code to interpret. This code has binary representation. See
     * it's description at the top of the file. It's set in run() method and used during
     * interpretation.
     */
    var _code = null;
    /**
     * {Object} Labels map. Key - label name, value - label line index started from zero.
     */
    var _labels = {};
    /**
     * {Array} Array of variables values. Every variable has it's own unique index
     * started from zero. We use these indexes in different command. e.g.:
     *
     *     0000 0001 0002 0004
     *          move two  four
     */
    var _vars = [];
    /**
     * {Array} Available commands by index. It's very important to keep these indexes
     * in a correct way, because all scripts will be broken.
     */
    var _cmds = [
        _set,    // 0
        _move,   // 1
        _inc     // 2
    ];

    /**
     * 'set' command handler. Initializes variable by specific value
     * Example: set 0001 two -> 0000 0001 0002
     * @param {Number} i Index of current code line
     */
    function _set(i) {
        _vars[_code[i + 3]] = _code[i + 2];
    }
    /**
     * move command handler. Moves value from first argument to second one.
     * Example: move one two -> 0000 0001 0001 0002
     * @param {Number} i Index of current code line
     */
    function _move(i) {
        _vars[_code[i + 3]] = _vars[_code[i + 2]];
    }
    /**
     * inc command handler. Increments variable.
     * Example: inc one # 0002 0001
     * @param {Number} i Index of current code line
     */
    function _inc(i) {
        _vars[_code[i + 2]]++;
    }

    //
    // public section
    //
    return {
        /**
         * Runs an interpreter
         * TODO:
         * @param {Uint16Array} code Lines of code in binary
         * @return {Boolean} true - all ok, false - not
         */
        run: function (code) {
            var i;
            var l = code.length;

            _code = code;
            //
            // All labels will be saved in _labels field
            //
            for (i = 0; i < l; i+=_LINE_SEGMENTS) {
                if (_code[i]) {_labels[_code[i]] = i;}
            }
            //
            // This is a main loop, where all commands are ran
            //
            i = 0;
            while (i < l) {
                i += (_cmds[code[i + 1]](i) || _LINE_SEGMENTS);
            }
        }
    };
})();<commit_msg>* Speed optimizations<commit_after>/**
 * Interpreter of evo language. Main goal of this interpreter is to run the
 * script as fast as possible. It interprets code line by line from up to the
 * bottom. This module doesn't have any checks (maybe only minimal) to increase
 * running speed. In general script looks like simple assembler and has binary
 * format stored in UInt16Array. Every line of code is _LINE_SEGMENTS
 * words (two bytes) in this array. So, for evo interpreter - script is a
 * binary array of UInt16 words. Take a look at simple script
 *
 *     0000 0000 0001 0000       # set  1    zero
 *     0000 0001 0000 0001       # move zero one
 *     0000 0002 0001            # inc  one
 *
 * One standard script line format is:
 *
 *     label cmd   arg1  arg2    # segments description
 *     0000  0001  0002  0003    # binary representation
 *  or      move  two   three   # readable representation
 *  or [0,   1,    2,    3]      # JS representation
 * where:
 *     label - Unique label number. 0000 means no label
 *     cmd   - Command, like move, add, inc, jump and so on
 *     arg1  - First argument. In most case the index of variable
 *     arg2  - Second argument. In most case the index of variable
 *
 * Previous example shows how script:
 *
 *     move two three
 *
 * is presented inside the interpreter. First 0000 means no label. Value greater
 * then 0000 means unique label number. Second 0001 means command index. For this
 * example is 'move' command. Two arguments 0002 and 0003 means two variables, with
 * appropriate indexes. So this line of code will move the value from variable
 * with index 0002 to variable with index 0003.
 *
 * Every command handler works in a similar way. It obtains only one parameter -
 * index of current command line in binary array. Using this index, current handler
 * may obtain all code line related info like: label, command and all arguments:
 *
 *     code[i + 0] label
 *     code[i + 1] cmd
 *     code[i + 2] arg1
 *     code[i + 3] arg2
 *
 * For example:
 *
 *     function _inc(i) {
 *         vars[code[i + 2]]++;
 *     }
 *
 * You should also note, that evo language should be read from left to the right.
 * For example command:
 *
 *     move two three
 *
 * Should be read as 'Move value of "two" variable into "three" variable' and not
 * like 'Move value of "three" variable into "two" variable'.
 *
 * Dependencies:
 *     Core
 *
 * Example:
 *     //
 *     // Binary representation of our two
 *     // lines script (4 words in each line)
 *     //
 *     var code = new Uint16Array(2 * 4);
 *
 *     // set 7 zero
 *     code[0]  = 0;  // no label
 *     code[1]  = 0;  // set
 *     code[2]  = 7;  // 7
 *     code[3]  = 0;  // zero var
 *
 *     // move zero one
 *     code[4]  = 0;  // no label
 *     code[5]  = 1;  // move
 *     code[6]  = 0;  // zero var
 *     code[7]  = 1;  // one var
 *
 *     Evo.Interpreter.run(code);
 *
 * @author DeadbraiN
 */
Evo.Interpreter = (function () {
    /**
     * @constant
     * {Number} Amount of segments (parts) in one script line: label, keyword, arg1, arg2
     */
    var _LINE_SEGMENTS = 4;
    /**
     * {Object} Labels map. Key - label name, value - label line index started from zero.
     */
    var _labels = {};
    /**
     * {Array} Array of variables values. Every variable has it's own unique index
     * started from zero. We use these indexes in different command. e.g.:
     *
     *     0000 0001 0002 0004
     *          move two  four
     */
    var _vars = [];
    /**
     * {Array} Available commands by index. It's very important to keep these indexes
     * in a correct way, because all scripts will be broken.
     */
    var _cmds = [
        _set,    // 0
        _move,   // 1
        _inc     // 2
    ];

    /**
     * 'set' command handler. Initializes variable by specific value
     * Example: 0000 0001 0002 # set 0001 two
     * @param {Array} vars Array of variable values by index
     * @param {Uint16Array} code Script in binary representation
     * @param {Number} i Index of current code line
     */
    function _set(vars, code, i) {
        vars[code[i + 3]] = code[i + 2];
    }
    /**
     * move command handler. Moves value from first argument to second one.
     * Example: move one two -> 0000 0001 0001 0002
     * @param {Array} vars Array of variable values by index
     * @param {Uint16Array} code Script in binary representation
     * @param {Number} i Index of current code line
     */
    function _move(vars, code, i) {
        vars[code[i + 3]] = vars[code[i + 2]];
    }
    /**
     * inc command handler. Increments variable.
     * Example: inc one # 0002 0001
     * @param {Array} vars Array of variable values by index
     * @param {Uint16Array} code Script in binary representation
     * @param {Number} i Index of current code line
     */
    function _inc(vars, code, i) {
        vars[code[i + 2]]++;
    }

    //
    // public section
    //
    return {
        /**
         * Runs an interpreter
         * TODO:
         * @param {Uint16Array} code Lines of code in binary
         * @return {Boolean} true - all ok, false - not
         */
        run: function (code) {
            var i;
            var l    = code.length;
            var vars = _vars;

            //
            // All labels will be saved in _labels field
            //
            for (i = 0; i < l; i+=_LINE_SEGMENTS) {
                if (code[i]) {_labels[code[i]] = i;}
            }
            //
            // This is a main loop, where all commands are ran
            //
            i = 0;
            while (i < l) {
                i += (_cmds[code[i + 1]](vars, code, i) || _LINE_SEGMENTS);
            }
        }
    };
})();<|endoftext|>


<commit_before>/* global task:true, desc, jake, fail, complete */

var conf  = require("./server/conf"),
    app   = require("./server/app"),
    log   = conf.log,
    color = require("cli-color");

/* TODO: Fix so it works with
 * prerequisites and3 async
 * ====================== */
// Keep old task function
var _task = task;

// Override task function
task = function() {
    var args = arguments;

    // Create task with old function
    var t = _task.apply(this, args);

    // Don't do anything if it's the default task or __root__
    if (t.name === "default" || t.name === "__root__") return _task.apply(this, args);

    // Save the old callback
    var _action = t.action;

    // Override the callback
    t.action = function() {
        // Inject logTask into callback
        logTask(t);

        // Call the old callback with the task as 'this' and the arguments passed by the callback
        _action.apply(t, t.args);
    };
};

task("default", ["lint", "test"]);

desc("Run the app");
task("run", function(port) {
    port = port || conf.PORT;
    app.listen(port, conf.IP, function() {
        // Using \u00A0 instead of a regular space because Cloud9 is a jerk
        // and prints 'Cloud9 Your application is running at ***' when it sees
        // 'Server listening on' in the console.. :(
        console.log(" " + color.green("Server\u00A0listening on %s:%s"), conf.IP, port);
        console.log(" " + color.bold.green("Ctrl+C to exit"));
    });
});

desc("Lint JavaScript files");
task("lint", function() {
    var lint = require("./server/lib/jake-jshint");
    var files = new jake.FileList();

    files.include("**/*.js");
    files.exclude(["node_modules"]);

    var pass = lint.run(files.toArray(), conf.lint.options, conf.lint.globals, function(pass) {
        if (!pass) fail("Lint failed");
        complete();
    });
}, { async: true });

desc("Run tests with Mocha");
task("test", function() {
    var MochaTest = require("mocha");
    var mocha = new MochaTest({ reporter: "spec", ui: "bdd" });
    var files = new jake.FileList();

    files.include("./server/test/_*_test.js");

    files.forEach(function(file) {
        mocha.addFile(file);
    });

    mocha.run(function(failures) {
        if (failures) fail("Mocha test failed");
        complete();
    });
}, { async: true });

desc("Testing stuff");
task("wat", function(strict) {
    if (strict === "true") log.info("STRICT");
    else log.info("NOT STRICT");

    log.info("Y U NO LOG");
});

function logTask(t) {
    console.log("\n" + color.blue("===== ") + color.bold.underline.yellow(t.description) + color.blue(" ====="));
}<commit_msg>Added some more comments on the c9 server listening bug<commit_after>/* global task:true, desc, jake, fail, complete */

var conf  = require("./server/conf"),
    app   = require("./server/app"),
    log   = conf.log,
    color = require("cli-color");

/* TODO: Fix so it works with
 * prerequisites and3 async
 * ====================== */
// Keep old task function
var _task = task;

// Override task function
task = function() {
    var args = arguments;

    // Create task with old function
    var t = _task.apply(this, args);

    // Don't do anything if it's the default task or __root__
    if (t.name === "default" || t.name === "__root__") return _task.apply(this, args);

    // Save the old callback
    var _action = t.action;

    // Override the callback
    t.action = function() {
        // Inject logTask into callback
        logTask(t);

        // Call the old callback with the task as 'this' and the arguments passed by the callback
        _action.apply(t, t.args);
    };
};

task("default", ["lint", "test"]);

desc("Run the app");
task("run", function(port) {
    port = port || conf.PORT;
    app.listen(port, conf.IP, function() {
        // Using \u00A0 instead of a regular space because Cloud9 is a jerk
        // and prints 'Cloud9 Your application is running at ***' when it sees
        // 'Server_listening_on' in the console.. :(
        // I also had to change the above line so it won't print when I 'cat Jakefile.js'
        // I submitted a bug report so hopefully this will get fixed soon..
        console.log(" " + color.green("Server\u00A0listening on %s:%s"), conf.IP, port);
        console.log(" " + color.bold.green("Ctrl+C to exit"));
    });
});

desc("Lint JavaScript files");
task("lint", function() {
    var lint = require("./server/lib/jake-jshint");
    var files = new jake.FileList();

    files.include("**/*.js");
    files.exclude(["node_modules"]);

    var pass = lint.run(files.toArray(), conf.lint.options, conf.lint.globals, function(pass) {
        if (!pass) fail("Lint failed");
        complete();
    });
}, { async: true });

desc("Run tests with Mocha");
task("test", function() {
    var MochaTest = require("mocha");
    var mocha = new MochaTest({ reporter: "spec", ui: "bdd" });
    var files = new jake.FileList();

    files.include("./server/test/_*_test.js");

    files.forEach(function(file) {
        mocha.addFile(file);
    });

    mocha.run(function(failures) {
        if (failures) fail("Mocha test failed");
        complete();
    });
}, { async: true });

desc("Testing stuff");
task("wat", function(strict) {
    if (strict === "true") log.info("STRICT");
    else log.info("NOT STRICT");

    log.info("Y U NO LOG");
});

function logTask(t) {
    console.log("\n" + color.blue("===== ") + color.bold.underline.yellow(t.description) + color.blue(" ====="));
}<|endoftext|>


<commit_before>/*
 * Copyright (c) 2015
 *
 * This file is licensed under the Affero General Public License version 3
 * or later.
 *
 * See the COPYING-README file.
 *
 */

/* global moment */

(function() {
	if (!OC.Share) {
		OC.Share = {};
	}

	var TEMPLATE =
			// currently expiration is only effective for link share.
			// this is about to change in future. Therefore this is not included
			// in the LinkShareView to ease reusing it in future. Then,
			// modifications (getting rid of IDs) are still necessary.
			'{{#if isLinkShare}}' +
			'<input type="checkbox" name="expirationCheckbox" class="expirationCheckbox checkbox" id="expirationCheckbox-{{cid}}" value="1" ' +
				'{{#if isExpirationSet}}checked="checked"{{/if}} {{#if disableCheckbox}}disabled="disabled"{{/if}} />' +
			'{{setExpirationLabel}}' +
			'
' +
			'    {{expirationLabel}}' +
			'    <input id="expirationDate" class="datepicker" type="text" placeholder="{{expirationDatePlaceholder}}" value="{{expirationValue}}" />' +
			'
' +
			'    {{#if isExpirationEnforced}}' +
				// originally the expire message was shown when a default date was set, however it never had text
			'{{defaultExpireMessage}}' +
			'    {{/if}}' +
			'{{/if}}'
		;

	/**
	 * @class OCA.Share.ShareDialogExpirationView
	 * @member {OC.Share.ShareItemModel} model
	 * @member {jQuery} $el
	 * @memberof OCA.Sharing
	 * @classdesc
	 *
	 * Represents the expiration part in the GUI of the share dialogue
	 *
	 */
	var ShareDialogExpirationView = OC.Backbone.View.extend({
		/** @type {string} **/
		id: 'shareDialogLinkShare',

		/** @type {OC.Share.ShareConfigModel} **/
		configModel: undefined,

		/** @type {Function} **/
		_template: undefined,

		/** @type {boolean} **/
		showLink: true,

		className: 'hidden',

		events: {
			'change .expirationCheckbox': '_onToggleExpiration',
			'change .datepicker': '_onChangeExpirationDate'
		},

		initialize: function(options) {
			if(!_.isUndefined(options.configModel)) {
				this.configModel = options.configModel;
			} else {
				throw 'missing OC.Share.ShareConfigModel';
			}

			var view = this;
			this.configModel.on('change:isDefaultExpireDateEnforced', function() {
				view.render();
			});

			this.model.on('change:itemType', function() {
				view.render();
			});

			this.model.on('change:linkShare', function() {
				view.render();
			});
		},

		_onToggleExpiration: function(event) {
			var $checkbox = $(event.target);
			var state = $checkbox.prop('checked');
			// TODO: slide animation
			this.$el.find('.expirationDateContainer').toggleClass('hidden', !state);
			if (!state) {
				// discard expiration date
				this.model.saveLinkShare({
					expireDate: ''
				});
			}
		},

		_onChangeExpirationDate: function(event) {
			var $target = $(event.target);
			$target.tooltip('hide');
			$target.removeClass('error');

			this.model.saveLinkShare({
				expiration: moment($target.val(), 'DD-MM-YYYY').format('YYYY-MM-DD')
			}, {
				error: function(model, message) {
					if (!message) {
						$target.attr('title', t('core', 'Error setting expiration date'));
					} else {
						$target.attr('title', message);
					}
					$target.tooltip({gravity: 'n'});
					$target.tooltip('show');
					$target.addClass('error');
				}
			});
		},

		render: function() {
			var defaultExpireMessage = '';
			var defaultExpireDays = this.configModel.get('defaultExpireDate');
			var isExpirationEnforced = this.configModel.get('isDefaultExpireDateEnforced');

			if(    (this.model.isFolder() || this.model.isFile())
				&& isExpirationEnforced) {
				defaultExpireMessage = t(
					'core',
					'The public link will expire no later than {days} days after it is created',
					{'days': defaultExpireDays }
				);
			}

			var isExpirationSet = !!this.model.get('linkShare').expiration || isExpirationEnforced;

			var expiration;
			if (isExpirationSet) {
				expiration = moment(this.model.get('linkShare').expiration, 'YYYY-MM-DD').format('DD-MM-YYYY');
			}

			this.$el.html(this.template({
				cid: this.cid,
				setExpirationLabel: t('core', 'Set expiration date'),
				expirationLabel: t('core', 'Expiration'),
				expirationDatePlaceholder: t('core', 'Expiration date'),
				defaultExpireMessage: defaultExpireMessage,
				isLinkShare: this.model.get('linkShare').isLinkShare,
				isExpirationSet: isExpirationSet,
				isExpirationEnforced: isExpirationEnforced,
				disableCheckbox: isExpirationEnforced && isExpirationSet,
				expirationValue: expiration
			}));

			// what if there is another date picker on that page?
			var minDate = new Date();
			var maxDate = null;
			// min date should always be the next day
			minDate.setDate(minDate.getDate()+1);

			if(isExpirationSet) {
				if(isExpirationEnforced) {
					// TODO: hack: backend returns string instead of integer
					var shareTime = this.model.get('linkShare').stime;
					if (_.isNumber(shareTime)) {
						shareTime = new Date(shareTime * 1000);
					}
					if (!shareTime) {
						shareTime = new Date(); // now
					}
					shareTime = OC.Util.stripTime(shareTime).getTime();
					maxDate = new Date(shareTime + defaultExpireDays * 24 * 3600 * 1000);
				}
			}
			$.datepicker.setDefaults({
				minDate: minDate,
				maxDate: maxDate
			});

			this.$el.find('.datepicker').datepicker({dateFormat : 'dd-mm-yy'});

			this.delegateEvents();

			return this;
		},

		/**
		 * @returns {Function} from Handlebars
		 * @private
		 */
		template: function (data) {
			if (!this._template) {
				this._template = Handlebars.compile(TEMPLATE);
			}
			return this._template(data);
		}

	});

	OC.Share.ShareDialogExpirationView = ShareDialogExpirationView;

})();
<commit_msg>[stable9.1] In cases where the server dictates the link share expiration the date is not updated on consequitive changes and enable/disable actions<commit_after>/*
 * Copyright (c) 2015
 *
 * This file is licensed under the Affero General Public License version 3
 * or later.
 *
 * See the COPYING-README file.
 *
 */

/* global moment */

(function() {
	if (!OC.Share) {
		OC.Share = {};
	}

	var TEMPLATE =
			// currently expiration is only effective for link share.
			// this is about to change in future. Therefore this is not included
			// in the LinkShareView to ease reusing it in future. Then,
			// modifications (getting rid of IDs) are still necessary.
			'{{#if isLinkShare}}' +
			'<input type="checkbox" name="expirationCheckbox" class="expirationCheckbox checkbox" id="expirationCheckbox-{{cid}}" value="1" ' +
				'{{#if isExpirationSet}}checked="checked"{{/if}} {{#if disableCheckbox}}disabled="disabled"{{/if}} />' +
			'{{setExpirationLabel}}' +
			'
' +
			'    {{expirationLabel}}' +
			'    <input id="expirationDate" class="datepicker" type="text" placeholder="{{expirationDatePlaceholder}}" value="{{expirationValue}}" />' +
			'
' +
			'    {{#if isExpirationEnforced}}' +
				// originally the expire message was shown when a default date was set, however it never had text
			'{{defaultExpireMessage}}' +
			'    {{/if}}' +
			'{{/if}}'
		;

	/**
	 * @class OCA.Share.ShareDialogExpirationView
	 * @member {OC.Share.ShareItemModel} model
	 * @member {jQuery} $el
	 * @memberof OCA.Sharing
	 * @classdesc
	 *
	 * Represents the expiration part in the GUI of the share dialogue
	 *
	 */
	var ShareDialogExpirationView = OC.Backbone.View.extend({
		/** @type {string} **/
		id: 'shareDialogLinkShare',

		/** @type {OC.Share.ShareConfigModel} **/
		configModel: undefined,

		/** @type {Function} **/
		_template: undefined,

		/** @type {boolean} **/
		showLink: true,

		className: 'hidden',

		events: {
			'change .expirationCheckbox': '_onToggleExpiration',
			'change .datepicker': '_onChangeExpirationDate'
		},

		initialize: function(options) {
			if(!_.isUndefined(options.configModel)) {
				this.configModel = options.configModel;
			} else {
				throw 'missing OC.Share.ShareConfigModel';
			}

			var view = this;
			this.configModel.on('change:isDefaultExpireDateEnforced', function() {
				view.render();
			});

			this.model.on('change:itemType', function() {
				view.render();
			});

			this.model.on('change:linkShare', function() {
				view.render();
			});
		},

		_onToggleExpiration: function(event) {
			var $checkbox = $(event.target);
			var state = $checkbox.prop('checked');
			// TODO: slide animation
			this.$el.find('.expirationDateContainer').toggleClass('hidden', !state);
			if (!state) {
				// discard expiration date
				this.model.get('linkShare').expiration = '';
				this.model.saveLinkShare({
					expireDate: ''
				});
			}
		},

		_onChangeExpirationDate: function(event) {
			var $target = $(event.target);
			$target.tooltip('hide');
			$target.removeClass('error');

			expiration = moment($target.val(), 'DD-MM-YYYY').format('YYYY-MM-DD');
			this.model.get('linkShare').expiration = expiration;
			this.model.saveLinkShare({
				expiration: expiration
			}, {
				error: function(model, message) {
					if (!message) {
						$target.attr('title', t('core', 'Error setting expiration date'));
					} else {
						$target.attr('title', message);
					}
					$target.tooltip({gravity: 'n'});
					$target.tooltip('show');
					$target.addClass('error');
				}
			});
		},

		render: function() {
			var defaultExpireMessage = '';
			var defaultExpireDays = this.configModel.get('defaultExpireDate');
			var isExpirationEnforced = this.configModel.get('isDefaultExpireDateEnforced');

			if(    (this.model.isFolder() || this.model.isFile())
				&& isExpirationEnforced) {
				defaultExpireMessage = t(
					'core',
					'The public link will expire no later than {days} days after it is created',
					{'days': defaultExpireDays }
				);
			}

			var isExpirationSet = !!this.model.get('linkShare').expiration || isExpirationEnforced;

			var expiration;
			if (isExpirationSet) {
				expiration = moment(this.model.get('linkShare').expiration, 'YYYY-MM-DD').format('DD-MM-YYYY');
			}

			this.$el.html(this.template({
				cid: this.cid,
				setExpirationLabel: t('core', 'Set expiration date'),
				expirationLabel: t('core', 'Expiration'),
				expirationDatePlaceholder: t('core', 'Expiration date'),
				defaultExpireMessage: defaultExpireMessage,
				isLinkShare: this.model.get('linkShare').isLinkShare,
				isExpirationSet: isExpirationSet,
				isExpirationEnforced: isExpirationEnforced,
				disableCheckbox: isExpirationEnforced && isExpirationSet,
				expirationValue: expiration
			}));

			// what if there is another date picker on that page?
			var minDate = new Date();
			var maxDate = null;
			// min date should always be the next day
			minDate.setDate(minDate.getDate()+1);

			if(isExpirationSet) {
				if(isExpirationEnforced) {
					// TODO: hack: backend returns string instead of integer
					var shareTime = this.model.get('linkShare').stime;
					if (_.isNumber(shareTime)) {
						shareTime = new Date(shareTime * 1000);
					}
					if (!shareTime) {
						shareTime = new Date(); // now
					}
					shareTime = OC.Util.stripTime(shareTime).getTime();
					maxDate = new Date(shareTime + defaultExpireDays * 24 * 3600 * 1000);
				}
			}
			$.datepicker.setDefaults({
				minDate: minDate,
				maxDate: maxDate
			});

			this.$el.find('.datepicker').datepicker({dateFormat : 'dd-mm-yy'});

			this.delegateEvents();

			return this;
		},

		/**
		 * @returns {Function} from Handlebars
		 * @private
		 */
		template: function (data) {
			if (!this._template) {
				this._template = Handlebars.compile(TEMPLATE);
			}
			return this._template(data);
		}

	});

	OC.Share.ShareDialogExpirationView = ShareDialogExpirationView;

})();
<|endoftext|>