зеркало из https://github.com/mozilla/pluotsorbet.git
424 строки
13 KiB
JavaScript
424 строки
13 KiB
JavaScript
/* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
|
/* vim: set shiftwidth=2 tabstop=2 autoindent cindent expandtab: */
|
|
|
|
'use strict';
|
|
|
|
function loadScript(path) {
|
|
return new Promise(function(resolve, reject) {
|
|
var element = document.createElement('script');
|
|
element.setAttribute("type", "text/javascript");
|
|
element.setAttribute("src", path);
|
|
document.getElementsByTagName("head")[0].appendChild(element);
|
|
element.onload = resolve;
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Pre-load dependencies and then load the main page.
|
|
*/
|
|
(function() {
|
|
var midletClassName = urlParams.midletClassName ? urlParams.midletClassName.replace(/\//g, '.') : "RunTests";
|
|
var loadingPromises = [];
|
|
if (midletClassName == "RunTests") {
|
|
loadingPromises.push(loadScript("tests/contacts.js"),
|
|
loadScript("tests/index.js"));
|
|
}
|
|
|
|
Promise.all(loadingPromises).then(function() {
|
|
document.getElementById("mozbrowser").src = "main.html" + location.search;
|
|
});
|
|
})();
|
|
|
|
var DumbPipe = {
|
|
// Functions that handle requests to open a pipe, indexed by type.
|
|
openers: {},
|
|
|
|
// Functions that receive messages from the other side for active pipes.
|
|
recipients: {},
|
|
|
|
// Every time we want to make the other side retrieve messages, the hash
|
|
// of the other side's web page has to change, so we increment it.
|
|
nextHashID: 0,
|
|
|
|
registerOpener: function(type, opener) {
|
|
this.openers[type] = opener;
|
|
},
|
|
|
|
handleEvent: function(event) {
|
|
if (event.detail.promptType == "custom-prompt") {
|
|
console.warn("unresponsive script warning; figure out how to handle");
|
|
return;
|
|
}
|
|
|
|
/**
|
|
* We embed messages in the mozbrowsershowmodalprompt event's detail.message
|
|
* property. The value of that property is a JSON string representing
|
|
* the message envelope, whose inner "message" property contains the actual
|
|
* message.
|
|
*
|
|
* @property command {String} the command to invoke: open|message|get|close
|
|
* @property type {String} the type of pipe to open (when command == open)
|
|
* @property pipeID {Number} unique ID (when command == open|message|close)
|
|
* @property message {String} the JSON message to forward to this side
|
|
*/
|
|
var envelope = JSON.parse(event.detail.message);
|
|
|
|
switch (envelope.command) {
|
|
case "open":
|
|
//console.log("outer recv: " + JSON.stringify(envelope));
|
|
this.openPipe(envelope.pipeID, envelope.type, envelope.message);
|
|
break;
|
|
case "message":
|
|
//console.log("outer recv: " + JSON.stringify(envelope));
|
|
this.receiveMessage(envelope.pipeID, envelope.message);
|
|
break;
|
|
case "close":
|
|
//console.log("outer recv: " + JSON.stringify(envelope));
|
|
this.closePipe(envelope.pipeID);
|
|
break;
|
|
}
|
|
},
|
|
|
|
openPipe: function(pipeID, type, message) {
|
|
var opener = this.openers[type];
|
|
|
|
if (!opener) {
|
|
console.error("no opener for pipe type " + type);
|
|
return;
|
|
}
|
|
|
|
// Create a function that this side of the boundary can use to send
|
|
// a message to the other side.
|
|
var sender = this.sendMessage.bind(this, pipeID);
|
|
|
|
this.recipients[pipeID] = opener(message, sender);
|
|
},
|
|
|
|
sendMessage: function(pipeID, message) {
|
|
// Sadly, we have no way to send a message to the other side directly.
|
|
// Instead, we change the hash part of the other side's URL, which triggers
|
|
// a hashchange event on the other side. A listener on the other side
|
|
// then sends us a "get" prompt, and we set its return value to the message.
|
|
// Oh my shod, that's some funky git!
|
|
var envelope = { pipeID: pipeID, message: message };
|
|
//console.log("outer send: " + JSON.stringify(envelope));
|
|
|
|
try {
|
|
document.getElementById("mozbrowser").contentWindow.postMessage(envelope, "*");
|
|
} catch (e) {
|
|
console.log("Error " + e + " while sending message: " + JSON.stringify(message));
|
|
}
|
|
},
|
|
|
|
receiveMessage: function(pipeID, message, detail) {
|
|
window.setZeroTimeout(function() {
|
|
if (!this.recipients[pipeID]) {
|
|
console.warn("nonexistent pipe " + pipeID + " received message " + JSON.stringify(message));
|
|
return;
|
|
}
|
|
|
|
try {
|
|
this.recipients[pipeID](message);
|
|
} catch(ex) {
|
|
console.error(ex + "\n" + ex.stack);
|
|
}
|
|
}.bind(this));
|
|
},
|
|
|
|
closePipe: function(pipeID) {
|
|
delete this.recipients[pipeID];
|
|
}
|
|
};
|
|
|
|
document.getElementById("mozbrowser").addEventListener("mozbrowsershowmodalprompt",
|
|
DumbPipe.handleEvent.bind(DumbPipe),
|
|
true);
|
|
|
|
DumbPipe.registerOpener("mobileInfo", function(message, sender) {
|
|
// Initialize the object with the URL params and fallback placeholders
|
|
// for testing/debugging on a desktop.
|
|
var mobileInfo = {
|
|
network: {
|
|
mcc: urlParams.network_mcc || "310", // United States
|
|
mnc: urlParams.network_mnc || "001",
|
|
},
|
|
icc: {
|
|
mcc: urlParams.icc_mcc || "310", // United States
|
|
mnc: urlParams.icc_mnc || "001",
|
|
msisdn: urlParams.icc_msisdn || "10005551212",
|
|
},
|
|
};
|
|
|
|
var mobileConnections = window.navigator.mozMobileConnections;
|
|
if (!mobileConnections && window.navigator.mozMobileConnection) {
|
|
mobileConnections = [ window.navigator.mozMobileConnection ];
|
|
}
|
|
|
|
// If we have access to the Mobile Connection API, then we use it to get
|
|
// the actual values.
|
|
if (mobileConnections) {
|
|
// Then the only part of the Mobile Connection API that is accessible
|
|
// to privileged apps is lastKnownNetwork and lastKnownHomeNetwork, which
|
|
// is fortunately all we need. lastKnownNetwork is a string of format
|
|
// "<mcc>-<mnc>", while lastKnownHomeNetwork is "<mcc>-<mnc>[-<spn>]".
|
|
// Use only the info about the first SIM for the time being.
|
|
var lastKnownNetwork = mobileConnections[0].lastKnownNetwork.split("-");
|
|
mobileInfo.network.mcc = lastKnownNetwork[0];
|
|
mobileInfo.network.mnc = lastKnownNetwork[1];
|
|
|
|
var lastKnownHomeNetwork = mobileConnections[0].lastKnownHomeNetwork.split("-");
|
|
mobileInfo.icc.mcc = lastKnownHomeNetwork[0];
|
|
mobileInfo.icc.mnc = lastKnownHomeNetwork[1];
|
|
}
|
|
|
|
sender(mobileInfo);
|
|
});
|
|
|
|
DumbPipe.registerOpener("contacts", function(message, sender) {
|
|
var req = navigator.mozContacts.getAll();
|
|
|
|
req.onsuccess = function() {
|
|
var contact = req.result;
|
|
// Transform the mozContact into a normal object, otherwise
|
|
// the pipe won't be able to send it.
|
|
sender(contact ? JSON.parse(JSON.stringify(contact)) : null);
|
|
if (contact) {
|
|
req.continue();
|
|
}
|
|
}
|
|
|
|
req.onerror = function() {
|
|
console.error("Error while reading contacts");
|
|
}
|
|
});
|
|
|
|
DumbPipe.registerOpener("socket", function(message, sender) {
|
|
var socket;
|
|
try {
|
|
socket = navigator.mozTCPSocket.open(message.host, message.port, { binaryType: "arraybuffer" });
|
|
} catch(ex) {
|
|
sender({ type: "error", error: "error opening socket" });
|
|
return function() {};
|
|
}
|
|
|
|
socket.onopen = function() {
|
|
sender({ type: "open" });
|
|
}
|
|
|
|
socket.onerror = function(event) {
|
|
sender({ type: "error", error: event.data.name });
|
|
}
|
|
|
|
socket.ondata = function(event) {
|
|
sender({ type: "data", data: event.data });
|
|
}
|
|
|
|
socket.ondrain = function(event) {
|
|
sender({ type: "drain" });
|
|
}
|
|
|
|
socket.onclose = function(event) {
|
|
sender({ type: "close" });
|
|
}
|
|
|
|
var send = function(data) {
|
|
// Convert the data back to an Int8Array.
|
|
data = new Int8Array(data);
|
|
|
|
try {
|
|
var result = socket.send(data.buffer, 0, data.length);
|
|
sender({ type: "send", result: result });
|
|
} catch (ex) {
|
|
sender({ type: "send", error: ex.toString() });
|
|
}
|
|
};
|
|
|
|
return function(message) {
|
|
switch (message.type) {
|
|
case "send":
|
|
send(message.data);
|
|
break;
|
|
case "close":
|
|
socket.close();
|
|
break;
|
|
}
|
|
};
|
|
});
|
|
|
|
DumbPipe.registerOpener("audiorecorder", function(message, sender) {
|
|
var mediaRecorder = null;
|
|
var localAudioStream = null;
|
|
|
|
function startRecording(localStream) {
|
|
localAudioStream = localStream;
|
|
|
|
mediaRecorder = new MediaRecorder(localStream, {
|
|
mimeType: message.mimeType // 'audio/3gpp' // need to be certified app.
|
|
});
|
|
|
|
mediaRecorder.ondataavailable = function(e) {
|
|
if (e.data.size == 0) {
|
|
return;
|
|
}
|
|
|
|
var fileReader = new FileReader();
|
|
fileReader.onload = function() {
|
|
sender({ type: "data", data: fileReader.result });
|
|
};
|
|
fileReader.readAsArrayBuffer(e.data);
|
|
};
|
|
|
|
mediaRecorder.onstop = function(e) {
|
|
// Do nothing here, just relay the event.
|
|
//
|
|
// We can't close the pipe here, one reason is |onstop| is fired before |ondataavailable|,
|
|
// if close pipe here, there is no chance to deliever the recorded voice. Another reason is
|
|
// the recording might be stopped and started back and forth. So let's do the pipe
|
|
// closing on the other side instead, i.e. DirectRecord::nClose.
|
|
sender({ type: "stop" });
|
|
};
|
|
|
|
mediaRecorder.onerror = function(e) {
|
|
sender({ type: "error" });
|
|
};
|
|
|
|
mediaRecorder.onpause = function(e) {
|
|
sender({ type: "pause" });
|
|
};
|
|
|
|
mediaRecorder.onstart = function(e) {
|
|
sender({ type: "start" });
|
|
};
|
|
|
|
mediaRecorder.start();
|
|
}
|
|
|
|
return function(message) {
|
|
switch(message.type) {
|
|
case "start":
|
|
try {
|
|
if (!mediaRecorder) {
|
|
navigator.mozGetUserMedia({
|
|
audio: true
|
|
}, function(localStream) {
|
|
startRecording(localStream);
|
|
}, function(e) {
|
|
sender({ type: "error", error: e });
|
|
});
|
|
} else if (mediaRecorder.state == "paused") {
|
|
mediaRecorder.resume();
|
|
} else {
|
|
mediaRecorder.start();
|
|
}
|
|
} catch (e) {
|
|
sender({ type: "error", error: e });
|
|
}
|
|
break;
|
|
case "requestData":
|
|
try {
|
|
// An InvalidState error might be thrown.
|
|
mediaRecorder.requestData();
|
|
} catch (e) {
|
|
sender({ type: "error", error: e });
|
|
}
|
|
break;
|
|
case "pause":
|
|
try {
|
|
mediaRecorder.pause();
|
|
} catch (e) {
|
|
sender({ type: "error", error: e });
|
|
}
|
|
break;
|
|
case "stop":
|
|
try {
|
|
mediaRecorder.stop();
|
|
localAudioStream.stop();
|
|
mediaRecorder = null;
|
|
localAudioStream = null;
|
|
} catch (e) {
|
|
sender({ type: "error", error: e });
|
|
}
|
|
break;
|
|
}
|
|
};
|
|
});
|
|
|
|
DumbPipe.registerOpener("camera", function(message, sender) {
|
|
var mediaStream = null;
|
|
|
|
var video = document.createElement("video");
|
|
document.body.appendChild(video);
|
|
video.style.position = "absolute";
|
|
video.style.visibility = "hidden";
|
|
// Some MIDlets need user touch/click on the screen to complete the snapshot,
|
|
// to make sure the MIDlet itself instead of the video element can capture
|
|
// the mouse/touch events, we need to set `pointer-events` as `none`.
|
|
video.style.pointerEvents = "none";
|
|
|
|
video.addEventListener('canplay', function(ev) {
|
|
// We should use videoWidth and videoHeight, but they are unavailable (https://bugzilla.mozilla.org/show_bug.cgi?id=926753)
|
|
var getDimensions = setInterval(function() {
|
|
if (video.videoWidth > 0 && video.videoHeight > 0) {
|
|
clearInterval(getDimensions);
|
|
sender({ type: "gotstream", width: video.videoWidth, height: video.videoHeight });
|
|
}
|
|
}, 50);
|
|
}, false);
|
|
|
|
navigator.mozGetUserMedia({
|
|
video: true,
|
|
audio: false,
|
|
}, function(localMediaStream) {
|
|
mediaStream = localMediaStream;
|
|
|
|
video.src = URL.createObjectURL(localMediaStream);
|
|
video.play();
|
|
}, function(err) {
|
|
console.log("Error: " + err);
|
|
});
|
|
|
|
return function(message) {
|
|
switch (message.type) {
|
|
case "setPosition":
|
|
video.style.left = message.x + "px";
|
|
video.style.top = message.y + "px";
|
|
video.style.width = message.w + "px";
|
|
video.style.height = message.h + "px";
|
|
break;
|
|
|
|
case "setVisible":
|
|
video.style.visibility = message.visible ? "visible" : "hidden";
|
|
break;
|
|
|
|
case "snapshot":
|
|
var canvas = document.createElement("canvas");
|
|
canvas.width = video.videoWidth;
|
|
canvas.height = video.videoHeight;
|
|
var ctx = canvas.getContext("2d");
|
|
ctx.drawImage(video, 0, 0, video.videoWidth, video.videoHeight);
|
|
|
|
canvas.toBlob(function(blob) {
|
|
var fileReader = new FileReader();
|
|
|
|
fileReader.onload = function(data) {
|
|
sender({ type: "snapshot", data: fileReader.result });
|
|
}
|
|
|
|
fileReader.readAsArrayBuffer(blob);
|
|
}, message.imageType);
|
|
break;
|
|
|
|
case "close":
|
|
if (mediaStream) {
|
|
mediaStream.stop();
|
|
}
|
|
|
|
if (video.parentNode) {
|
|
document.body.removeChild(video);
|
|
}
|
|
|
|
break;
|
|
}
|
|
};
|
|
});
|