{"action":"created","comment":{"url":"https://api.github.com/repos/mozilla-b2g/gecko-b2g/pulls/comments/62284751","id":62284751,"diff_hunk":"@@ -0,0 +1,395 @@\n+/* This Source Code Form is subject to the terms of the Mozilla Public\n+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,\n+ * You can obtain one at http://mozilla.org/MPL/2.0/. */\n+\n+/*\n+ * RemoteControlService.jsm is the entry point of remote control function.\n+ * The service initializes a TLS socket server to receives events from user.\n+ *\n+ *               RemoteControlService <-- Gecko Preference\n+ *\n+ *     user -->  nsITLSSocketServer --> script (gecko)\n+ *\n+ * Events from user are in JSON format. After parsed to control command,\n+ * these events are passed to script (js), runs in sandbox,\n+ * and dispatch corresponding events to Gecko.\n+ *\n+ * Here is related component location:\n+ * gecko/b2g/components/RemoteControlService.jsm\n+ *\n+ * For more details, please visit: https://wiki.mozilla.org/Firefox_OS/Remote_Control\n+ */\n+\n+\"use strict\";\n+\n+this.EXPORTED_SYMBOLS = [\"RemoteControlService\"];\n+\n+const { classes: Cc, interfaces: Ci, results: Cr, utils: Cu, Constructor: CC } = Components;\n+\n+Cu.import(\"resource://gre/modules/Services.jsm\");\n+Cu.import(\"resource://gre/modules/XPCOMUtils.jsm\");\n+Cu.import(\"resource://gre/modules/debug.js\");\n+\n+XPCOMUtils.defineLazyServiceGetter(this, \"certService\", \"@mozilla.org/security/local-cert-service;1\",\n+                                   \"nsILocalCertService\");\n+\n+// static functions\n+function debug(aStr) {\n+  dump(\"RemoteControlService: \" + aStr + \"\\n\");\n+}\n+\n+const DEBUG = false;\n+\n+const REMOTE_CONTROL_EVENT = 'mozChromeRemoteControlEvent';\n+const RC_PREF_DEVICES = \"remotecontrol.authorized_devices\";\n+const MAX_CLIENT_CONNECTIONS = 5; // Allow max 5 clients to use remote control TV\n+\n+const SERVER_STATUS = {\n+  STOPPED: 0,\n+  STARTED: 1\n+};\n+\n+this.RemoteControlService = {\n+  // Remote Control status\n+  _serverStatus: SERVER_STATUS.STOPPED,\n+\n+  // TLS socket server\n+  _port: -1, // The port on which this service listens\n+  _socket: null, // The socket associated with this\n+  _doQuit: false, // Indicates when the service is to be shut down at the end of the request.\n+  _socketClosed: true, // True if the socket in this is closed, false otherwise.\n+  _connectionGen: 0, // Used for tracking existing connections\n+  _connections: new Map(), // Hash of all open connections, indexed by connection number\n+  _mDNSCancelableHandler: null, // Handle mDNS cancel\n+\n+  init: function() {\n+    DEBUG && debug(\"init\");\n+  },\n+\n+  // PUBLIC API\n+  // Start TLS socket server.\n+  // Return a promise for start() resolves/reject to\n+  start: function() {\n+    if (this._serverStatus == SERVER_STATUS.STARTED) {\n+      return Promise.reject(\"AlreadyStarted\");\n+    }\n+\n+    let promise = new Promise((aResolve, aReject) => {\n+      this._doStart(aResolve, aReject);\n+    });\n+    return promise;\n+  },\n+\n+  // Stop TLS socket server, remove registered observer\n+  // Cancel mDNS registration\n+  // Return false if server not started, stop failed.\n+  stop: function() {\n+    if (this._serverStatus == SERVER_STATUS.STOPPED) {\n+      return false;\n+    }\n+\n+    if (!this._socket) {\n+      return false;\n+    }\n+\n+    DEBUG && debug(\"Stop listening on port \" + this._socket.port);\n+\n+    Services.obs.removeObserver(this, \"xpcom-shutdown\");\n+\n+    if (this._mDNSCancelableHandler) {\n+      this._mDNSCancelableHandler.Cancel(Cr.NS_OK);\n+      this._mDNSCancelableHandler = null;\n+    }\n+\n+    this._socket.close();\n+    this._socket = null;\n+    this._doQuit = false;\n+    this._serverStatus = SERVER_STATUS.STOPPED;\n+\n+    return true;\n+  },\n+\n+  // Observers and Listeners\n+  // nsIObserver\n+  observe: function(subject, topic, data) {\n+    switch (topic) {\n+      case \"xpcom-shutdown\": {\n+        // Stop service when xpcom-shutdown\n+        this.stop();\n+        break;\n+      }\n+    }\n+  },\n+\n+  // nsIServerSocketListener\n+  onSocketAccepted: function(socket, trans) {\n+    DEBUG && debug(\"onSocketAccepted(socket=\" + socket + \", trans=\" + trans + \")\");\n+    DEBUG && debug(\"New connection on \" + trans.host + \":\" + trans.port);\n+\n+    const SEGMENT_SIZE = 8192;\n+    const SEGMENT_COUNT = 1024;\n+\n+    try {\n+      var input = trans.openInputStream(0, SEGMENT_SIZE, SEGMENT_COUNT)\n+                       .QueryInterface(Ci.nsIAsyncInputStream);\n+      var output = trans.openOutputStream(0, 0, 0);\n+    } catch (e) {\n+      DEBUG && debug(\"Error opening transport streams: \" + e);\n+      trans.close(Cr.NS_BINDING_ABORTED);\n+      return;\n+    }\n+\n+    let connectionNumber = ++this._connectionGen;\n+\n+    try {\n+      // Create a connection for each user connection\n+      // EventHandler implements nsIInputStreamCallback for incoming message from user\n+      var conn = new Connection(input, output, this, socket.port, trans.port, connectionNumber);\n+      let handler = new EventHandler(conn);\n+\n+      input.asyncWait(handler, 0, 0, Services.tm.mainThread);\n+    } catch (e) {\n+      DEBUG && debug(\"Error in initial connection: \" + e);\n+      trans.close(Cr.NS_BINDING_ABORTED);\n+      return;\n+    }\n+\n+    this._connections.set(connectionNumber, conn);\n+    DEBUG && debug(\"Start connection \" + connectionNumber);\n+  },\n+\n+  // Close all connection when socket closed\n+  onStopListening: function(socket, status) {\n+    DEbug && debug(\"Shut down server on port \" + socket.port);\n+\n+    this._connections.forEach(function(value, key){\n+      let connection = value;\n+      connection.close();\n+    });\n+\n+    this._socketClosed = true;\n+  },\n+\n+  // PRIVATE FUNCTIONS\n+  _doStart: function(aResolve, aReject) {\n+    DEBUG && debug(\"doStart\");\n+\n+    if (this._socket) {\n+      aReject(\"SocketAlreadyInit\");\n+      return;\n+    }\n+\n+    this._doQuit = this._socketClosed = false;\n+\n+    // Monitor xpcom-shutdown to stop service and clean up\n+    Services.obs.addObserver(this, \"xpcom-shutdown\", false);\n+\n+    // Start TLSSocketServer with self-signed certification\n+    Cc[\"@mozilla.org/psm;1\"].getService(Ci.nsISupports);\n+    certService.getOrCreateCert(\"RemoteControlService\", {\n+      handleCert: function(cert, result) {\n+        if(result) {\n+          aReject(\"getOrCreateCert \" + result);\n+        } else {\n+          let self = RemoteControlService;\n+\n+          try {\n+            // Try to get random port\n+            let ios = Cc[\"@mozilla.org/network/io-service;1\"].getService(Ci.nsIIOService);\n+            let socket;\n+            for (let i = 100; i; i--) {\n+              let temp = Cc[\"@mozilla.org/network/tls-server-socket;1\"].createInstance(Ci.nsITLSServerSocket);\n+              temp.init(self._port, false, MAX_CLIENT_CONNECTIONS);\n+              temp.serverCert = cert;\n+\n+              let allowed = ios.allowPort(temp.port, \"tls\");\n+              if (!allowed) {\n+                DEBUG && debug(\"Warning: obtained TLSServerSocket listens on a blocked port: \" + temp.port);\n+              }\n+\n+              if (!allowed && self._port == -1) {\n+                DEBUG && debug(\"Throw away TLSServerSocket with bad port.\");\n+                temp.close();\n+                continue;\n+              }\n+\n+              socket = temp;\n+              break;\n+            }\n+\n+            if (!socket) {\n+              throw new Error(\"No socket server available. Are there no available ports?\");\n+            }\n+\n+            DEBUG && debug(\"Listen on port \" + socket.port + \", \" + MAX_CLIENT_CONNECTIONS + \" pending connections\");\n+\n+            socket.serverCert = cert;\n+            socket.setSessionCache(false);\n+            socket.setSessionTickets(false);\n+            socket.setRequestClientCertificate(Ci.nsITLSServerSocket.REQUEST_NEVER);\n+\n+            socket.asyncListen(self);\n+            self._port = socket.port;\n+            self._socket = socket;\n+          } catch (e) {\n+            DEBUG && debug(\"Could not start server on port \" + self._port + \": \" + e);\n+            aReject(\"Start TLSSocketServer fail\");\n+          }\n+\n+          // Register mDNS remote control service with this._port\n+          if ((\"@mozilla.org/toolkit/components/mdnsresponder/dns-sd;1\" in Cc)) {\n+            let serviceInfo = Cc[\"@mozilla.org/toolkit/components/mdnsresponder/dns-info;1\"].createInstance(Ci.nsIDNSServiceInfo);\n+            serviceInfo.serviceType = \"_remotecontrol._tcp\";\n+            serviceInfo.serviceName = Services.prefs.getCharPref(\"dom.presentation.device.name\");\n+            serviceInfo.port = self._port;\n+\n+            let mdns = Cc[\"@mozilla.org/toolkit/components/mdnsresponder/dns-sd;1\"].getService(Ci.nsIDNSServiceDiscovery);\n+            self._mDNSCancelableHandler = mdns.registerService(serviceInfo, null);\n+          }\n+\n+          aResolve();\n+          self._serverStatus = SERVER_STATUS.STARTED;\n+        }\n+      }\n+    });\n+  },\n+\n+  // Notifies this server that the given connection has been closed.\n+  _connectionClosed: function(connection) {\n+    NS_ASSERT(connection.number in this._connections,\n+              \"closing a connection \" + this + \" that we never added to the \" +\n+              \"set of open connections?\");\n+    NS_ASSERT(this._connections.get(connection.number) === connection,\n+              \"connection number mismatch?  \" +\n+              this._connections.get(connection.number));\n+    delete this._connections.get(connection.number);\n+  },\n+};\n+\n+// Represents a connection to the server\n+function Connection(input, output, server, port, outgoingPort, number) {\n+  DEBUG && debug(\"Open a new connection \" + number + \" on port \" + outgoingPort);\n+\n+  // Stream of incoming data\n+  this.input = input;\n+\n+  // Stream for outgoing data\n+  this.output = output;\n+\n+  // Server associated with this connection\n+  this.server = server;\n+\n+  // Port on which the server is running\n+  this.port = port;\n+\n+  // Outgoing poort used by this connection\n+  this._outgoingPort = outgoingPort;\n+\n+  // The serial number of this connection\n+  this.number = number;\n+\n+  // This allows a connection to disambiguate between a peer initiating a\n+  // close and the socket being forced closed on shutdown.\n+  this._closed = false;\n+}\n+Connection.prototype = {\n+  // Closes this connection's input/output streams\n+  close: function() {\n+    if (this._closed) {\n+      return;\n+    }\n+\n+    DEBUG && debug(\"Close connection \" + this.number + \" on port \" + this._outgoingPort);\n+\n+    this.input.close();\n+    this.output.close();\n+    this._closed = true;\n+\n+    let server = this.server;\n+    server._connectionClosed(this);\n+\n+    // If an error triggered a server shutdown, act on it now\n+    if (server._doQuit) {\n+      server.stop();\n+    }\n+  },\n+};\n+\n+function streamClosed(e) {\n+  return e === Cr.NS_BASE_STREAM_CLOSED ||\n+         (typeof e === \"object\" && e.result === Cr.NS_BASE_STREAM_CLOSED);\n+}\n+\n+// Parse and dispatch incoming events from client\n+function EventHandler(connection) {\n+  this._connection = connection;\n+\n+  this._output = Cc[\"@mozilla.org/intl/converter-output-stream;1\"]\n+                   .createInstance(Ci.nsIConverterOutputStream);\n+  this._output.init(this._connection.output, \"UTF-8\", 0, 0x0000);\n+\n+  this._input = Cc[\"@mozilla.org/intl/converter-input-stream;1\"]\n+                  .createInstance(Ci.nsIConverterInputStream);\n+  this._input.init(this._connection.input, \"UTF-8\", 0,\n+                   Ci.nsIConverterInputStream.DEFAULT_REPLACEMENT_CHARACTER);\n+}\n+EventHandler.prototype = {\n+  // nsIInputStreamCallback\n+  onInputStreamReady: function(input) {\n+    DEBUG && debug(\"onInputStreamReady(input=\" + input + \") on thread \" +\n+                   Services.tm.currentThread + \" (main is \" +\n+                   Services.tm.mainThread + \")\");\n+\n+    try {\n+      let available = 0, numChars = 0, fullMessage = \"\";\n+\n+      // Read and concat messages from input stream buffer\n+      do {\n+        let partialMessage = {};\n+\n+        available = input.available();\n+        numChars = this._input.readString(available, partialMessage);\n+\n+        fullMessage += partialMessage.value;\n+      } while(numChars < available);\n+\n+      if (numChars != 0 && fullMessage.length > 0) { // While last readString contains something\n+        // Handle concatenated JSON string\n+        fullMessage = '[' + fullMessage.replace(/}{/g, '},{') + ']';\n+\n+        try {\n+          // Parse JSON string to event objects\n+          let events = JSON.parse(fullMessage);\n+\n+          events.forEach((event) => {\n+            this._handleEvent(event);\n+          });\n+        } catch (e) {\n+          DEBUG && debug (\"Parse event error, drop this message\");\n+        }\n+      }\n+    } catch (e) {\n+      if (streamClosed(e)) {\n+        DEBUG && debug(\"WARNING: unexpected error when reading from socket; will \" +\n+                       \"be treated as if the input stream had been closed\");\n+        DEBUG && debug(\"WARNING: actual error was: \" + e);\n+      }\n+\n+      // Input has been closed, but we're still expecting to read more data.\n+      // available() will throw in this case, destroy the connection.\n+      DEBUG && debug(\"onInputStreamReady called on a closed input, destroying connection\");\n+      this._connection.close();\n+      return;\n+    }\n+\n+    // Wait next message\n+    input.asyncWait(this, 0, 0, Services.tm.currentThread);\n+  },\n+\n+  // PRIVATE FUNCTIONS\n+  _handleEvent: function(event) {\n+    // TODO: Implement JPAKE pairing and control command dispatch here\n+  },\n+};\n+\n+RemoteControlService.init();","path":"b2g/components/RemoteControlService.jsm","position":395,"original_position":395,"commit_id":"e5f7124be9d2189cd7b24d1176d9114154f198a4","original_commit_id":"e5f7124be9d2189cd7b24d1176d9114154f198a4","user":{"login":"schien","id":1792048,"avatar_url":"https://avatars.githubusercontent.com/u/1792048?v=3","gravatar_id":"","url":"https://api.github.com/users/schien","html_url":"https://github.com/schien","followers_url":"https://api.github.com/users/schien/followers","following_url":"https://api.github.com/users/schien/following{/other_user}","gists_url":"https://api.github.com/users/schien/gists{/gist_id}","starred_url":"https://api.github.com/users/schien/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/schien/subscriptions","organizations_url":"https://api.github.com/users/schien/orgs","repos_url":"https://api.github.com/users/schien/repos","events_url":"https://api.github.com/users/schien/events{/privacy}","received_events_url":"https://api.github.com/users/schien/received_events","type":"User","site_admin":false},"body":"`RemoteControlService.init()` does nothing but print a debug log. please remove it.","created_at":"2016-05-06T03:16:26Z","updated_at":"2016-05-06T03:16:26Z","html_url":"https://github.com/mozilla-b2g/gecko-b2g/pull/2#discussion_r62284751","pull_request_url":"https://api.github.com/repos/mozilla-b2g/gecko-b2g/pulls/2","_links":{"self":{"href":"https://api.github.com/repos/mozilla-b2g/gecko-b2g/pulls/comments/62284751"},"html":{"href":"https://github.com/mozilla-b2g/gecko-b2g/pull/2#discussion_r62284751"},"pull_request":{"href":"https://api.github.com/repos/mozilla-b2g/gecko-b2g/pulls/2"}}},"pull_request":{"url":"https://api.github.com/repos/mozilla-b2g/gecko-b2g/pulls/2","id":68971972,"html_url":"https://github.com/mozilla-b2g/gecko-b2g/pull/2","diff_url":"https://github.com/mozilla-b2g/gecko-b2g/pull/2.diff","patch_url":"https://github.com/mozilla-b2g/gecko-b2g/pull/2.patch","issue_url":"https://api.github.com/repos/mozilla-b2g/gecko-b2g/issues/2","number":2,"state":"open","locked":false,"title":"Bug 1197749 - Add RemoteControlService for remote control feature","user":{"login":"MDTsai","id":5763237,"avatar_url":"https://avatars.githubusercontent.com/u/5763237?v=3","gravatar_id":"","url":"https://api.github.com/users/MDTsai","html_url":"https://github.com/MDTsai","followers_url":"https://api.github.com/users/MDTsai/followers","following_url":"https://api.github.com/users/MDTsai/following{/other_user}","gists_url":"https://api.github.com/users/MDTsai/gists{/gist_id}","starred_url":"https://api.github.com/users/MDTsai/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/MDTsai/subscriptions","organizations_url":"https://api.github.com/users/MDTsai/orgs","repos_url":"https://api.github.com/users/MDTsai/repos","events_url":"https://api.github.com/users/MDTsai/events{/privacy}","received_events_url":"https://api.github.com/users/MDTsai/received_events","type":"User","site_admin":false},"body":"Add --disable-remote-control-service in b2g/moz.configure for the feature.\r\n\r\nRemoteControlService starts TLS socket server, listen and parse incoming message to event.\r\n\r\nBase on schien comment on 8746461, fix:\r\n1. default turn on service\r\n2. Use random port\r\n3. Use Map for _connections\r\n4. Add broadcast port throught mDNS\r\n5. Use nsIConverterOutputStream and nsIConverterInputStream for getting UTF-8 string from all output and input stream\r\n6. Handle partial string from stream buffer and concatenated string to events","created_at":"2016-05-05T06:47:24Z","updated_at":"2016-05-06T03:16:26Z","closed_at":null,"merged_at":null,"merge_commit_sha":"beff3886ea1bcc60d83e1f180e0774d22b2619bb","assignee":null,"milestone":null,"commits_url":"https://api.github.com/repos/mozilla-b2g/gecko-b2g/pulls/2/commits","review_comments_url":"https://api.github.com/repos/mozilla-b2g/gecko-b2g/pulls/2/comments","review_comment_url":"https://api.github.com/repos/mozilla-b2g/gecko-b2g/pulls/comments{/number}","comments_url":"https://api.github.com/repos/mozilla-b2g/gecko-b2g/issues/2/comments","statuses_url":"https://api.github.com/repos/mozilla-b2g/gecko-b2g/statuses/e5f7124be9d2189cd7b24d1176d9114154f198a4","head":{"label":"MDTsai:2.6_Bug_1197749","ref":"2.6_Bug_1197749","sha":"e5f7124be9d2189cd7b24d1176d9114154f198a4","user":{"login":"MDTsai","id":5763237,"avatar_url":"https://avatars.githubusercontent.com/u/5763237?v=3","gravatar_id":"","url":"https://api.github.com/users/MDTsai","html_url":"https://github.com/MDTsai","followers_url":"https://api.github.com/users/MDTsai/followers","following_url":"https://api.github.com/users/MDTsai/following{/other_user}","gists_url":"https://api.github.com/users/MDTsai/gists{/gist_id}","starred_url":"https://api.github.com/users/MDTsai/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/MDTsai/subscriptions","organizations_url":"https://api.github.com/users/MDTsai/orgs","repos_url":"https://api.github.com/users/MDTsai/repos","events_url":"https://api.github.com/users/MDTsai/events{/privacy}","received_events_url":"https://api.github.com/users/MDTsai/received_events","type":"User","site_admin":false},"repo":{"id":43104148,"name":"gecko-dev","full_name":"MDTsai/gecko-dev","owner":{"login":"MDTsai","id":5763237,"avatar_url":"https://avatars.githubusercontent.com/u/5763237?v=3","gravatar_id":"","url":"https://api.github.com/users/MDTsai","html_url":"https://github.com/MDTsai","followers_url":"https://api.github.com/users/MDTsai/followers","following_url":"https://api.github.com/users/MDTsai/following{/other_user}","gists_url":"https://api.github.com/users/MDTsai/gists{/gist_id}","starred_url":"https://api.github.com/users/MDTsai/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/MDTsai/subscriptions","organizations_url":"https://api.github.com/users/MDTsai/orgs","repos_url":"https://api.github.com/users/MDTsai/repos","events_url":"https://api.github.com/users/MDTsai/events{/privacy}","received_events_url":"https://api.github.com/users/MDTsai/received_events","type":"User","site_admin":false},"private":false,"html_url":"https://github.com/MDTsai/gecko-dev","description":"Read-only Git mirror of the Mercurial gecko repositories at https://hg.mozilla.org. Please don't submit PRs, see: https://developer.mozilla.org/en-US/docs/Mercurial_FAQ#I%27m_all_used_to_git.2C_but_how_can_I_provide_Mercurial-ready_patches_.3F","fork":true,"url":"https://api.github.com/repos/MDTsai/gecko-dev","forks_url":"https://api.github.com/repos/MDTsai/gecko-dev/forks","keys_url":"https://api.github.com/repos/MDTsai/gecko-dev/keys{/key_id}","collaborators_url":"https://api.github.com/repos/MDTsai/gecko-dev/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/MDTsai/gecko-dev/teams","hooks_url":"https://api.github.com/repos/MDTsai/gecko-dev/hooks","issue_events_url":"https://api.github.com/repos/MDTsai/gecko-dev/issues/events{/number}","events_url":"https://api.github.com/repos/MDTsai/gecko-dev/events","assignees_url":"https://api.github.com/repos/MDTsai/gecko-dev/assignees{/user}","branches_url":"https://api.github.com/repos/MDTsai/gecko-dev/branches{/branch}","tags_url":"https://api.github.com/repos/MDTsai/gecko-dev/tags","blobs_url":"https://api.github.com/repos/MDTsai/gecko-dev/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/MDTsai/gecko-dev/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/MDTsai/gecko-dev/git/refs{/sha}","trees_url":"https://api.github.com/repos/MDTsai/gecko-dev/git/trees{/sha}","statuses_url":"https://api.github.com/repos/MDTsai/gecko-dev/statuses/{sha}","languages_url":"https://api.github.com/repos/MDTsai/gecko-dev/languages","stargazers_url":"https://api.github.com/repos/MDTsai/gecko-dev/stargazers","contributors_url":"https://api.github.com/repos/MDTsai/gecko-dev/contributors","subscribers_url":"https://api.github.com/repos/MDTsai/gecko-dev/subscribers","subscription_url":"https://api.github.com/repos/MDTsai/gecko-dev/subscription","commits_url":"https://api.github.com/repos/MDTsai/gecko-dev/commits{/sha}","git_commits_url":"https://api.github.com/repos/MDTsai/gecko-dev/git/commits{/sha}","comments_url":"https://api.github.com/repos/MDTsai/gecko-dev/comments{/number}","issue_comment_url":"https://api.github.com/repos/MDTsai/gecko-dev/issues/comments{/number}","contents_url":"https://api.github.com/repos/MDTsai/gecko-dev/contents/{+path}","compare_url":"https://api.github.com/repos/MDTsai/gecko-dev/compare/{base}...{head}","merges_url":"https://api.github.com/repos/MDTsai/gecko-dev/merges","archive_url":"https://api.github.com/repos/MDTsai/gecko-dev/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/MDTsai/gecko-dev/downloads","issues_url":"https://api.github.com/repos/MDTsai/gecko-dev/issues{/number}","pulls_url":"https://api.github.com/repos/MDTsai/gecko-dev/pulls{/number}","milestones_url":"https://api.github.com/repos/MDTsai/gecko-dev/milestones{/number}","notifications_url":"https://api.github.com/repos/MDTsai/gecko-dev/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/MDTsai/gecko-dev/labels{/name}","releases_url":"https://api.github.com/repos/MDTsai/gecko-dev/releases{/id}","deployments_url":"https://api.github.com/repos/MDTsai/gecko-dev/deployments","created_at":"2015-09-25T02:10:46Z","updated_at":"2015-09-25T02:20:08Z","pushed_at":"2016-05-05T09:02:04Z","git_url":"git://github.com/MDTsai/gecko-dev.git","ssh_url":"git@github.com:MDTsai/gecko-dev.git","clone_url":"https://github.com/MDTsai/gecko-dev.git","svn_url":"https://github.com/MDTsai/gecko-dev","homepage":"https://developer.mozilla.org/docs/Introduction","size":2714301,"stargazers_count":0,"watchers_count":0,"language":"C++","has_issues":false,"has_downloads":true,"has_wiki":false,"has_pages":false,"forks_count":0,"mirror_url":null,"open_issues_count":0,"forks":0,"open_issues":0,"watchers":0,"default_branch":"master"}},"base":{"label":"mozilla-b2g:b2g48_v2_6","ref":"b2g48_v2_6","sha":"ccf59a9dda868db58b5d943e2c4235ead89f9344","user":{"login":"mozilla-b2g","id":1261292,"avatar_url":"https://avatars.githubusercontent.com/u/1261292?v=3","gravatar_id":"","url":"https://api.github.com/users/mozilla-b2g","html_url":"https://github.com/mozilla-b2g","followers_url":"https://api.github.com/users/mozilla-b2g/followers","following_url":"https://api.github.com/users/mozilla-b2g/following{/other_user}","gists_url":"https://api.github.com/users/mozilla-b2g/gists{/gist_id}","starred_url":"https://api.github.com/users/mozilla-b2g/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/mozilla-b2g/subscriptions","organizations_url":"https://api.github.com/users/mozilla-b2g/orgs","repos_url":"https://api.github.com/users/mozilla-b2g/repos","events_url":"https://api.github.com/users/mozilla-b2g/events{/privacy}","received_events_url":"https://api.github.com/users/mozilla-b2g/received_events","type":"Organization","site_admin":false},"repo":{"id":57910513,"name":"gecko-b2g","full_name":"mozilla-b2g/gecko-b2g","owner":{"login":"mozilla-b2g","id":1261292,"avatar_url":"https://avatars.githubusercontent.com/u/1261292?v=3","gravatar_id":"","url":"https://api.github.com/users/mozilla-b2g","html_url":"https://github.com/mozilla-b2g","followers_url":"https://api.github.com/users/mozilla-b2g/followers","following_url":"https://api.github.com/users/mozilla-b2g/following{/other_user}","gists_url":"https://api.github.com/users/mozilla-b2g/gists{/gist_id}","starred_url":"https://api.github.com/users/mozilla-b2g/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/mozilla-b2g/subscriptions","organizations_url":"https://api.github.com/users/mozilla-b2g/orgs","repos_url":"https://api.github.com/users/mozilla-b2g/repos","events_url":"https://api.github.com/users/mozilla-b2g/events{/privacy}","received_events_url":"https://api.github.com/users/mozilla-b2g/received_events","type":"Organization","site_admin":false},"private":false,"html_url":"https://github.com/mozilla-b2g/gecko-b2g","description":"For use of TV team - Commits to b2g48_v2_6 done here","fork":true,"url":"https://api.github.com/repos/mozilla-b2g/gecko-b2g","forks_url":"https://api.github.com/repos/mozilla-b2g/gecko-b2g/forks","keys_url":"https://api.github.com/repos/mozilla-b2g/gecko-b2g/keys{/key_id}","collaborators_url":"https://api.github.com/repos/mozilla-b2g/gecko-b2g/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/mozilla-b2g/gecko-b2g/teams","hooks_url":"https://api.github.com/repos/mozilla-b2g/gecko-b2g/hooks","issue_events_url":"https://api.github.com/repos/mozilla-b2g/gecko-b2g/issues/events{/number}","events_url":"https://api.github.com/repos/mozilla-b2g/gecko-b2g/events","assignees_url":"https://api.github.com/repos/mozilla-b2g/gecko-b2g/assignees{/user}","branches_url":"https://api.github.com/repos/mozilla-b2g/gecko-b2g/branches{/branch}","tags_url":"https://api.github.com/repos/mozilla-b2g/gecko-b2g/tags","blobs_url":"https://api.github.com/repos/mozilla-b2g/gecko-b2g/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/mozilla-b2g/gecko-b2g/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/mozilla-b2g/gecko-b2g/git/refs{/sha}","trees_url":"https://api.github.com/repos/mozilla-b2g/gecko-b2g/git/trees{/sha}","statuses_url":"https://api.github.com/repos/mozilla-b2g/gecko-b2g/statuses/{sha}","languages_url":"https://api.github.com/repos/mozilla-b2g/gecko-b2g/languages","stargazers_url":"https://api.github.com/repos/mozilla-b2g/gecko-b2g/stargazers","contributors_url":"https://api.github.com/repos/mozilla-b2g/gecko-b2g/contributors","subscribers_url":"https://api.github.com/repos/mozilla-b2g/gecko-b2g/subscribers","subscription_url":"https://api.github.com/repos/mozilla-b2g/gecko-b2g/subscription","commits_url":"https://api.github.com/repos/mozilla-b2g/gecko-b2g/commits{/sha}","git_commits_url":"https://api.github.com/repos/mozilla-b2g/gecko-b2g/git/commits{/sha}","comments_url":"https://api.github.com/repos/mozilla-b2g/gecko-b2g/comments{/number}","issue_comment_url":"https://api.github.com/repos/mozilla-b2g/gecko-b2g/issues/comments{/number}","contents_url":"https://api.github.com/repos/mozilla-b2g/gecko-b2g/contents/{+path}","compare_url":"https://api.github.com/repos/mozilla-b2g/gecko-b2g/compare/{base}...{head}","merges_url":"https://api.github.com/repos/mozilla-b2g/gecko-b2g/merges","archive_url":"https://api.github.com/repos/mozilla-b2g/gecko-b2g/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/mozilla-b2g/gecko-b2g/downloads","issues_url":"https://api.github.com/repos/mozilla-b2g/gecko-b2g/issues{/number}","pulls_url":"https://api.github.com/repos/mozilla-b2g/gecko-b2g/pulls{/number}","milestones_url":"https://api.github.com/repos/mozilla-b2g/gecko-b2g/milestones{/number}","notifications_url":"https://api.github.com/repos/mozilla-b2g/gecko-b2g/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/mozilla-b2g/gecko-b2g/labels{/name}","releases_url":"https://api.github.com/repos/mozilla-b2g/gecko-b2g/releases{/id}","deployments_url":"https://api.github.com/repos/mozilla-b2g/gecko-b2g/deployments","created_at":"2016-05-02T18:14:06Z","updated_at":"2016-05-02T18:19:40Z","pushed_at":"2016-05-05T06:47:24Z","git_url":"git://github.com/mozilla-b2g/gecko-b2g.git","ssh_url":"git@github.com:mozilla-b2g/gecko-b2g.git","clone_url":"https://github.com/mozilla-b2g/gecko-b2g.git","svn_url":"https://github.com/mozilla-b2g/gecko-b2g","homepage":"https://wiki.mozilla.org/TV_2.6","size":2748829,"stargazers_count":0,"watchers_count":0,"language":null,"has_issues":false,"has_downloads":true,"has_wiki":false,"has_pages":false,"forks_count":0,"mirror_url":null,"open_issues_count":1,"forks":0,"open_issues":1,"watchers":0,"default_branch":"b2g48_v2_6"}},"_links":{"self":{"href":"https://api.github.com/repos/mozilla-b2g/gecko-b2g/pulls/2"},"html":{"href":"https://github.com/mozilla-b2g/gecko-b2g/pull/2"},"issue":{"href":"https://api.github.com/repos/mozilla-b2g/gecko-b2g/issues/2"},"comments":{"href":"https://api.github.com/repos/mozilla-b2g/gecko-b2g/issues/2/comments"},"review_comments":{"href":"https://api.github.com/repos/mozilla-b2g/gecko-b2g/pulls/2/comments"},"review_comment":{"href":"https://api.github.com/repos/mozilla-b2g/gecko-b2g/pulls/comments{/number}"},"commits":{"href":"https://api.github.com/repos/mozilla-b2g/gecko-b2g/pulls/2/commits"},"statuses":{"href":"https://api.github.com/repos/mozilla-b2g/gecko-b2g/statuses/e5f7124be9d2189cd7b24d1176d9114154f198a4"}}}}