I have a simple flash socket that I use to connect to IRC servers. It has an open, close, and send method made available to JS through ExternalInterface, for opening connections, closing connections, and sending messages respectively. The socket calls IRC.io.receive in JS whenever it gets a message, which is parsed by JS into something useful.
Unfortunately, whenever any of the flash methods are called from JS, they return a "__ is not a function" error.
Here's the (watered down) AS, where IRC is the document class:
public class IRC extends MovieClip {
public static function open(url:String, port:int) {/* code */}
public static function close(port:int) {/* code */}
public static function send(port:int, message:String) {/* code */}
public function IRC() {
ExternalInterface.addCallback('send', IRC.send);
ExternalInterface.addCallback('open', IRC.open);
ExternalInterface.addCallback('close', IRC.close);
}
}
And HTML/JS:
<html>
<head>
<script type="text/javascript">
window.IRC = {
io: {}
};
IRC.io.receive = function(message) {/* code */}
IRC.io.send = function(port, str) {
document.getElementById('socket').send(port, str);
}
IRC.io.open = function(url, port) {
document.getElementById('socket').open(url, port);
}
IRC.io.close = function(port) {
document.getElementById('socket').close(port);
}
</script>
</head>
<body>
<!-- ui -->
<embed src="socket.swf" quality="high" allowscriptsaccess="always" pluginspage="http://www.macromedia.com/go/getflashplayer" type="application/x-shockwave-flash" allowfullscreen="false" style="display:none;">
</body>
<html>
Any call to any of the functions registered with ExternalInterface throws a "function does not exist" exception. Did I do something wrong?
Try signalling from your swf when it's ready to receive calls.
For example, in your ActionScript:
ExternalInterface.call('initIRQ');
And in your JavaScript:
function initIRQ() {
//Begin communication with swf
}
Related
I would like to prevent Server-Side Blazor from firing the OnSubmit event handler (that I can't get rid of for some other reasons) of the EditForm when I hit Enter in the input field. It seems to be an easy task, and I just can't solve it.
I made up the smallest example I could think of:
_Index.razor:
#page "/"
#inject IJSRuntime JSRuntime;
<EditForm Model="Model" OnSubmit="HandleOnSubmit">
<input id="testInput"/>
<button type="submit" value="unneeded submit button"></button>
</EditForm>
#code
{
private CModel Model { get; set; } = new CModel() { ID = "ID", Name = "Name" };
private async Task HandleOnSubmit()
{
await JSRuntime.InvokeAsync<object>("alert", new object[] { "This alert shouldn't be shown!!" });
}
}
the CModel class (although it has no relevance to the question):
public class CModel
{
public string ID { get; set; }
public string Name { get; set; }
}
and the _Host.cshtml:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>PreventSubmitOnForm</title>
<base href="~/" />
<link rel="stylesheet" href="css/bootstrap/bootstrap.min.css" />
<link href="css/site.css" rel="stylesheet" />
</head>
<body>
<app>
<component type="typeof(App)" render-mode="ServerPrerendered" />
</app>
<div id="blazor-error-ui">
<environment include="Staging,Production">
An error has occurred. This application may no longer respond until reloaded.
</environment>
<environment include="Development">
An unhandled exception has occurred. See browser dev tools for details.
</environment>
Reload
<a class="dismiss">🗙</a>
</div>
<script src="_framework/blazor.server.js"></script>
<script>
document.getElementById("testInput").addEventListener('keypress', function (event) {
if (event.which === 13) {
event.preventDefault();
alert('Form submit prevented');
}
});
</script>
</body>
</html>
In the _Host.cshtml I register a new keypress eventhandler, and as I debug, on page creation it will be really registered. But it never will be triggered on keypress Enter.
And I already have tried to trick the situation with #onkeypress:preventDefault and #onkeypress:stopPropagation but they don't seem to help (as Steve Sanderson declared in his issue comment) when I want to prevent the default behavior only for my input field and in that specific field only for the event: Enter pressed.
In a normal HTML + JS case it works like a charm: https://jsfiddle.net/wmk608gh/1/
So I ended up by the JS Interop solution and added the script
<script>
document.getElementById("testInput").addEventListener('keypress', function (event) {
if (event.which === 13) {
event.preventDefault();
alert('Form submit prevented');
}
});
</script>
to the _Host.cshtml.
But yet it doesn't seem to work. I presume I'm supposed to register my script to another event (instead of keypress), or maybe there is already a best practice I could follow here.
Any help appreciated.
The problem is that you try to add the EventListener at a time where your <input /> element is not rendered yet.
To make this work you can call your JS function after blazor has rendered your page:
In your _Index.razor #code block add:
protected override async Task OnAfterRenderAsync(bool firstRender)
{
if (firstRender)
{
await JSRuntime.InvokeVoidAsync("PreventDefault");
}
}
In _Host.cshtml wrap your addEventListener with a function:
<script>
function PreventDefault() {
document.getElementById("testInput").addEventListener('keypress', function (event) {
if (event.which === 13) {
event.preventDefault();
alert('Form submit prevented');
}
});
}
</script>
I am integrating jquery and zk project.
My goal is to pass value from js/jquery side to java side but in vain.
Here is the code I reference: use zAu to send data from client to server
However, there exists the error:
java.lang.ClassCastException: org.zkoss.zk.ui.event.MouseEvent cannot be cast to org.zkoss.zk.ui.event.ForwardEvent
I have seen some other person saying that we must cast the mouseevent to forwardevent in order to get a NOT NULL getData() value.
At my java side:
public class TryHttpLenovo extends SelectorComposer<Component> {
#Listen("onClick=#btnHttp")
public void serverReceive(Event e) {
ForwardEvent forwardE = (ForwardEvent) e;
System.out.println("forwardE.getData()"+forwardE.getData());
}
}
In my http.zul:
<window apply="foo.TryHttpLenovo" xmlns:w="client">
<button id="btnHttp" w:onClick="sentToServer();">http send</button>
</window>
In my testhttp.js:
function sentToServer(){
var wgt=zk.Widget.$('btnHttp');
zAu.send(new zk.Event(wgt, "serverReceive", {foo: 'my data'}, {toServer:true}));
}
After several trial-and-error, I finally solve this!!!!
The solution is to extend GenericForwardComposer.
I also adjust some other things, but the only important change is to extend GenericForwardComposer instead of SelectorComposer.
The #Listen annotation is not needed in my solution.
in .java
public class TryHttpV2 extends GenericForwardComposer {
public void onUser2$info(Event event) {
ForwardEvent forwardE = (ForwardEvent) event;
System.out.println("forwardE.getOrigin().getData(): " + forwardE.getOrigin().getData());
}
}
in .js
function sendToServer(){
payload = "using generic composer";
zAu.send(new zk.Event(zk.Widget.$(this), 'onUser2', payload));
}
in .zul
<?page title="try using generic composer" contentType="text/html;charset=UTF-8"?>
<?script src="/js/tryhttp_v2.js" ?>
<zk xmlns="http://www.zkoss.org/2005/zul">
<window id="info" apply="foo.TryHttpV2" xmlns:w="client">
<button id="btnExec" w:onClick="sendToServer();" label="to be tested button" />
</window>
</zk>
I'm developing Google Cast custom receiver app using WebTorrent (https://webtorrent.io, https://github.com/feross/webtorrent) and Google Cast sender app using JavaScript (Chrome) SDK.
The idea of my app is sending torrent id (magnet URI like magnet:?xt=urn:btih:6a9759bffd5c0af65319979fb7832189f4f3c35d or HTTP/HTTPS URL to a *.torrent file like https://webtorrent.io/torrents/sintel.torrent) from Google Cast sender to Google Cast receiver, and using WebTorrent in Google Cast receiver to display the media (video or audio) from the torrent.
Note that torrent id is not a direct URL to the media file.
Now I'm using Google Cast namespace and messageBus to send and receive the torrent id.
WebTorrent API provides 2 ways to display the media:
append it to the DOM using file.appendTo: https://webtorrent.io/docs#-file-appendto-rootelem-function-callback-err-elem-
render directly into given element (or CSS selector) using file.renderTo: https://webtorrent.io/docs#-file-renderto-elem-function-callback-err-elem-
Here is the code of my receiver:
<html>
<head>
<script src="https://www.gstatic.com/cast/sdk/libs/receiver/2.0.0/cast_receiver.js"></script>
<script src="https://cdn.jsdelivr.net/webtorrent/latest/webtorrent.min.js"></script>
</head>
<body>
<video autoplay id='media' />
<script>
window.mediaElement = document.getElementById('media');
window.mediaManager = new cast.receiver.MediaManager(window.mediaElement);
window.castReceiverManager = cast.receiver.CastReceiverManager.getInstance();
window.messageBus = window.castReceiverManager.getCastMessageBus('urn:x-cast:com.google.cast.sample.helloworld');
window.messageBus.onMessage = function(event) {
displayVideo(event.data);
// Inform all senders on the CastMessageBus of the incoming message event
// sender message listener will be invoked
window.messageBus.send(event.senderId, event.data);
};
function displayVideo(torrentId) {
var client = new WebTorrent();
client.add(torrentId, function (torrent) {
var file = torrent.files[0];
file.renderTo('video');
});
}
window.castReceiverManager.start();
</script>
</body>
</html>
Here is the code of my sender:
<!--
Copyright (C) 2014 Google Inc. All Rights Reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<html>
<head>
<style type="text/css">
html, body, #wrapper {
height:100%;
width: 100%;
margin: 0;
padding: 0;
border: 0;
}
#wrapper td {
vertical-align: middle;
text-align: center;
}
input {
font-family: "Arial", Arial, sans-serif;
font-size: 40px;
font-weight: bold;
}
.border {
border: 2px solid #cccccc;
border-radius: 5px;
}
.border:focus {
outline: none;
border-color: #8ecaed;
box-shadow: 0 0 5px #8ecaed;
}
</style>
<script type="text/javascript" src="https://www.gstatic.com/cv/js/sender/v1/cast_sender.js"></script>
<script type="text/javascript">
var applicationID = 'F5304A3D';
var namespace = 'urn:x-cast:com.google.cast.sample.helloworld';
var session = null;
/**
* Call initialization for Cast
*/
if (!chrome.cast || !chrome.cast.isAvailable) {
setTimeout(initializeCastApi, 1000);
}
/**
* initialization
*/
function initializeCastApi() {
var sessionRequest = new chrome.cast.SessionRequest(applicationID);
var apiConfig = new chrome.cast.ApiConfig(sessionRequest,
sessionListener,
receiverListener);
chrome.cast.initialize(apiConfig, onInitSuccess, onError);
};
/**
* initialization success callback
*/
function onInitSuccess() {
appendMessage("onInitSuccess");
}
/**
* initialization error callback
*/
function onError(message) {
appendMessage("onError: "+JSON.stringify(message));
}
/**
* generic success callback
*/
function onSuccess(message) {
appendMessage("onSuccess: "+message);
}
/**
* callback on success for stopping app
*/
function onStopAppSuccess() {
appendMessage('onStopAppSuccess');
}
/**
* session listener during initialization
*/
function sessionListener(e) {
appendMessage('New session ID:' + e.sessionId);
session = e;
session.addUpdateListener(sessionUpdateListener);
session.addMessageListener(namespace, receiverMessage);
}
/**
* listener for session updates
*/
function sessionUpdateListener(isAlive) {
var message = isAlive ? 'Session Updated' : 'Session Removed';
message += ': ' + session.sessionId;
appendMessage(message);
if (!isAlive) {
session = null;
}
};
/**
* utility function to log messages from the receiver
* #param {string} namespace The namespace of the message
* #param {string} message A message string
*/
function receiverMessage(namespace, message) {
appendMessage("receiverMessage: "+namespace+", "+message);
};
/**
* receiver listener during initialization
*/
function receiverListener(e) {
if( e === 'available' ) {
appendMessage("receiver found");
}
else {
appendMessage("receiver list empty");
}
}
/**
* stop app/session
*/
function stopApp() {
session.stop(onStopAppSuccess, onError);
}
/**
* send a message to the receiver using the custom namespace
* receiver CastMessageBus message handler will be invoked
* #param {string} message A message string
*/
function sendMessage(message) {
if (session!=null) {
session.sendMessage(namespace, message, onSuccess.bind(this, "Message sent: " + message), onError);
}
else {
chrome.cast.requestSession(function(e) {
session = e;
session.sendMessage(namespace, message, onSuccess.bind(this, "Message sent: " + message), onError);
}, onError);
}
}
/**
* append message to debug message window
* #param {string} message A message string
*/
function appendMessage(message) {
console.log(message);
var dw = document.getElementById("debugmessage");
dw.innerHTML += '\n' + JSON.stringify(message);
};
/**
* utility function to handle text typed in by user in the input field
*/
function update() {
sendMessage(document.getElementById("input").value);
}
/**
* handler for the transcribed text from the speech input
* #param {string} words A transcibed speech string
*/
function transcribe(words) {
sendMessage(words);
}
</script>
</head>
<body>
<table id="wrapper">
<tr>
<td>
<form method="get" action="JavaScript:update();">
<input id="input" class="border" type="text" size="30" onwebkitspeechchange="transcribe(this.value)" x-webkit-speech/>
</form>
</td>
</tr>
</table>
<!-- Debbugging output -->
<div style="margin:10px; visibility:hidden;">
<textarea rows="20" cols="70" id="debugmessage">
</textarea>
</div>
<script type="text/javascript">
document.getElementById("input").focus();
</script>
</body>
</html>
The problem: The receiver handles torrent id from sender and video plays as expected. But official Google Cast app or official Google Cast extension for Chrome doesn't show standard media controls for playing video to pause, stop, seek, etc.
This is what I have (this is a screenshot of standard built-in modal dialog for Google Cast in the latest version of Google Chrome):
This is what I want to achieve (this is a screenshot of standard built-in modal dialog for Google Cast in the latest version of Google Chrome):
Adding
window.mediaElement = document.getElementById('media');
window.mediaManager = new cast.receiver.MediaManager(window.mediaElement);
for
<video autoplay id='media' />
element don't help.
Should I add something to sender and/or receiver to add standard media controls for <video autoplay id='media' /> on all senders?
Maybe there is another way to send and receive torrent id without using Google Cast namespace and messageBus?
UPD
Looks like I've found the root of my problem...
How to enable default media controls for existing playing video in receiver?
For example, the receiver app already have playing video:
<video autoplay id='media'
src='https://commondatastorage.googleapis.com/gtv-videos-bucket/sample/BigBuckBunny.mp4'
/>
How to enable default media controls - working buttons "Play/Pause", working progress bar (on all senders like official Google Cast extension for Chrome) for this playing video?
Looks like adding the following code not help:
window.mediaElement = document.getElementById('media');
window.mediaManager = new cast.receiver.MediaManager(window.mediaElement);
window.castReceiverManager = cast.receiver.CastReceiverManager.getInstance();
window.castReceiverManager.start();
Here is the full source code of receiver:
<html>
<head>
<script src="https://www.gstatic.com/cast/sdk/libs/receiver/2.0.0/cast_receiver.js"></script>
</head>
<body>
<video autoplay id='media'
src='https://commondatastorage.googleapis.com/gtv-videos-bucket/sample/BigBuckBunny.mp4'
/>
<script>
window.mediaElement = document.getElementById('media');
window.mediaManager = new cast.receiver.MediaManager(window.mediaElement);
window.castReceiverManager = cast.receiver.CastReceiverManager.getInstance();
window.castReceiverManager.start();
</script>
</body>
</html>
UPD2:
Looks like it is possible to use any text string (the torrent id in my case) instead of media URL in chrome.cast.media.MediaInfo and use the media namespace instead of using custom namespace and custom message bus (i.e. without using https://developers.google.com/cast/docs/reference/receiver/cast.receiver.CastReceiverManager#getCastMessageBus and https://developers.google.com/cast/docs/reference/receiver/cast.receiver.CastMessageBus and https://developers.google.com/cast/docs/reference/chrome/chrome.cast.Session#sendMessage):
function cast() {
url = 'magnet:?xt=urn:btih:6a9759bffd5c0af65319979fb7832189f4f3c35d';
chrome.cast.requestSession(function(session) {
var mediaInfo = new chrome.cast.media.MediaInfo(url);
//mediaInfo.contentType = 'video/mp4';
//mediaInfo.contentType = 'audio/mpeg';
//mediaInfo.contentType = 'image/jpeg';
var request = new chrome.cast.media.LoadRequest(mediaInfo);
request.autoplay = true;
session.loadMedia(request, function() {}, onError);
}, onError);
}
But how to handle it on the receiver in this case?
There is actually an existing Google Cast UX Guidelines which states that the sender application must provide a top-level Cast button. There are three ways to support a Cast button which were fully discussed in Android Sender App Development
Using the MediaRouter ActionBar provider:
android.support.v7.app.MediaRouteActionProvider
Using the MediaRouter Cast button:
android.support.v7.app.MediaRouteButton
Developing a custom UI with the MediaRouter API’s and
MediaRouter.Callback
To show standard media controls once the media is playing, the sender application can control the media playback using the RemoteMediaPlayer instance. Steps and examples can be found in the documentation.
For a comprehensive listing of all classes, methods and events in the Google Cast Android SDK, see the Google Cast Android API Reference.
I realize it's been 3 years, but what jumps out at me is that you are missing the "controls" attribute on your video tag! Without the attribute, a player will render the video but provide no ui for controlling playback....
Syntax is the same as it is for autoplay: the controls attribute is standalone and takes no value. It is all or nothing - a standard set of controls with default styling, or none at all... if you were building for the browser, you might choose to omit the attribute and make your own controls to match the look and feel of the page. But based on the screenshots you shared, there's no need (and it might not work in a chromecast receiver environment)
The correct html is below, and that should be all you need :)
https://commondatastorage.googleapis.com/gtv-videos-bucket/sample/BigBuckBunny.mp4'
/>
Please let us know if and how you ended up fixing your problem (and send me a DM... I recently started integrating webtorrent into a streaming video platform I'm creating and so far, so good, but the documentation is pretty bad and I have a few questions. Thanks!)
I am running into an issue where all google app endpoints are running smoothly. No issue at getting, updating or deleting entries while using the API explorer.
I am running into issue when trying to mesh all this together in an html file.
Updates and deletes work fine... Only get/list methods are returning no record.
Any idea what the issue is? What is best to troubleshoot this kind of issue?
Thanks.
test.java
import com.googlecode.objectify.annotation.*;
#Entity
#Index
public class Test {
#Id Long number;
Long number2;
public Test(){
}
public Test(Long number, Long number2){
this.number = number;
this.number2 = number2;
}
public Test(Long number2){
this.number2 = number2;
}
public Long getNumber2() {
return number2;
}
public void setNumber2(Long number2) {
this.number2 = number2;
}
public Long getNumber() {
return number;
}
public void setNumber(Long number) {
this.number = number;
}
}
test.html
<html>
<head>
<title></title>
</head>
<body>
<div>
<form>
<div>
<input type="text" id="insert" value="2"><br><br>
<button type="submit" id="getTestButton" onclick="getTest()" disabled>Get</button>
</div>
</form>
<p id="result"></p>
<hr>
</div>
<!--
Load the Google APIs Client Library for JavaScript
More info here : https://developers.google.com/api-client- library/javascript/reference/referencedocs
-->
<script type="text/javascript">
function init() {
gapi.client.load('testEndpoint', 'v1', enableButton, 'http://localhost:8080/_ah/api');
document.getElementById('getTestButton').onclick = function() {
getTest();
}
}
function enableButton() {
console.log("enabling button");
getTestButton.disabled = false;
}
function getTest() {
console.log("entering getTest function");
var features = {};
features.number2 = document.getElementById("insert").value;
console.log(features);
var req = gapi.client.testEndpoint.getTest(features);
req.execute(function(data) {
console.log(data);
document.getElementById("result").innerHTML = data.number + " " + data.number2;
});
}
</script>
<script src="https://apis.google.com/js/client.js?onload=init"></script>
</body>
</html>
TestEndpoint.java
import com.google.api.server.spi.config.Api;
import com.google.api.server.spi.config.ApiMethod;
import com.google.api.server.spi.config.ApiNamespace;
import java.util.logging.Logger;
import javax.inject.Named;
import com.googlecode.objectify.cmd.Query;
import static com.Backend.OfyService.ofy;
/** An endpoint class we are exposing */
#Api(name = "testEndpoint", version = "v1", namespace = #ApiNamespace(ownerDomain = "Backend.com", ownerName = "Backend.com", packagePath=""))
public class TestEndpoint {
// Make sure to add this endpoint to your web.xml file if this is a web application.
private static final Logger LOG = Logger.getLogger(TestEndpoint.class.getName());
/**
* This method gets the <code>Test</code> object associated with the specified <code>id</code>.
* #param id The id of the object to be returned.
* #return The <code>Test</code> associated with <code>id</code>.
*/
#ApiMethod(name = "getTest")
public Test getTest(#Named("number2") Long number2) {
// Implement this function
LOG.info("***GetTest***NUMBER=" + number2);
Test t = new Test(1L, 2L);
//t = ofy().load().type(Test.class).filter("number2", number2).first().now();
LOG.info("t.getNumber()==> " + t.getNumber().toString());
LOG.info("Calling getTest method");
return t;
}
}
Seems legit to me. To isolate the cause of the error, I would suggest to do the following :
Test your getTest endpoint method through the API explorer, located at http://yourAppAddress/_ah/api/explorer . If it works you know the issue is in your client code.
If it did not work, then the issue is either with the code inside getTest or with the way you use endpoints (or with endpoints itself). To check this, create a pure Java servlet that will call the getTest() method directly, and log the result. If it works you know the issue is either on the way you use endpoints or on endpoints itself.
Sorry I don't have an immediate answer to your question.
EDIT : Since you tell me that the API Explorer test works, I checked your javascript code and there's something weird : you try to get resp.items.length in the callback. But your endpoint method only returns a Test entity, so there are no items attribute.
Can you try this code ?
gapi.client.testEndpoint.getTest(requestData).execute(function(resp) {
console.log(resp);
});
EDIT 2 : So you've found the cause of the issue by yourself : you cannot call the endpoints method before the API client is loaded. Since your method is called by an onClick event, probably on a button, you must do the following :
By default, disable the button
In the gapi.client.load 's callback, enable the button
That way you will be sure to only click on the button when the Google service is initialized.
EDIT 3 : It is actually a problem in your html. You have a submit button, which makes Chrome reload the page. Every time you click the button the page reloads and you never see the result of your endpoint query.
This HTML file works for me on the dev server :
<html>
<head>
<title></title>
</head>
<body>
<div>
<div>
<input type="text" id="insert" value="2"><br><br>
<button id="getTestButton" onclick="getTest()" disabled>Get</button>
</div>
<p id="result"></p>
<hr>
</div>
<!--
Load the Google APIs Client Library for JavaScript
More info here : https://developers.google.com/api-client- library/javascript/reference/referencedocs
-->
<script type="text/javascript">
function init() {
gapi.client.load('testEndpoint', 'v1', enableButton, '/_ah/api');
document.getElementById('getTestButton').onclick = function () {
getTest();
}
}
function enableButton() {
console.log("enabling button");
getTestButton.disabled = false;
}
function getTest() {
console.log("entering getTest function");
var features = {};
features.number2 = document.getElementById("insert").value;
console.log(features);
var req = gapi.client.testEndpoint.getTest(features);
req.execute(function (data) {
console.log(data);
document.getElementById("result").innerHTML = data.number + " " + data.number2;
});
}
</script>
<script src="https://apis.google.com/js/client.js?onload=init"></script>
</body>
</html>
I'm trying to get the most basic XPCOM javascript object to be accessible to the javascript I load into my webpage. I'm using the example code from this tutorial:
https://developer.mozilla.org/en-US/docs/How_to_Build_an_XPCOM_Component_in_Javascript
Here is my set up:
install.rdf:
<?xml version="1.0"?>
<RDF xmlns="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:em="http://www.mozilla.org/2004/em-rdf#">
<Description about="urn:mozilla:install-manifest">
<em:id>helloworld#thellamatesting.com</em:id>
<em:name>Hello World</em:name>
<em:version>1.0</em:version>
<em:type>2</em:type>
<em:creator>The Llama</em:creator>
<em:description>Testing</em:description>
<em:targetApplication>
<Description>
<em:id>{ec8030f7-c20a-464f-9b0e-13a3a9e97384}</em:id>
<em:minVersion>2.0</em:minVersion>
<em:maxVersion>20.0</em:maxVersion>
</Description>
</em:targetApplication>
</Description>
</RDF>
chrome.manifest
content helloworld chrome/content/
content helloworld chrome/content/ contentaccessible=yes
overlay chrome://browser/content/browser.xul chrome://helloworld/content/browser.xul
component {4762b5c0-5b32-11e2-bcfd-0800200c9a66} components/HelloWorld.js
contract #thellamatesting.com/helloworld;1 {4762b5c0-5b32-11e2-bcfd-0800200c9a66}
locale helloworld en-US locale/en-US/
components/HelloWorld.js
Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
function HelloWorld() {
// If you only need to access your component from Javascript, uncomment the following line:
this.wrappedJSObject = this;
}
HelloWorld.prototype = {
classDescription: "My Hello World Javascript XPCOM Component",
classID: Components.ID("{4762b5c0-5b32-11e2-bcfd-0800200c9a66}"),
//Also tried
//classID: Components.ID("4762b5c0-5b32-11e2-bcfd-0800200c9a66"),
contractID: "#thellamatesting.com/helloworld;1",
QueryInterface: XPCOMUtils.generateQI(),
// Also tried
//QueryInterface: XPCOMUtils.generateQI([Components.interfaces.nsIHelloWorld]),
hello: function() {
return "Hello World!";
}
};
var components = [HelloWorld];
if ("generateNSGetFactory" in XPCOMUtils)
var NSGetFactory = XPCOMUtils.generateNSGetFactory(components); // Firefox 4.0 and higher
else
var NSGetModule = XPCOMUtils.generateNSGetModule(components); // Firefox 3.x
Testing HTML:
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<title></title>
<script type="application/javascript">
function go() {
try {
var coms = Components;
alert(Components.classes);
var myComponent = Components.classes['#thellamatesting.com/helloworld;1'].getService().wrappedJSObject;
alert(myComponent.hello());
} catch (anError) {
dump("ERROR: " + anError);
}
};
</script>
</head>
<body>
<button onclick="javascript:go()">Click to go</button>
</body>
</html>
After all this, I end up with "Components.classes is undefined". Does anyone know what I'm doing wrong here?
Thanks so much!
In order to gain access to the Components object from a javascript context, you need to have extended capabilities, that is, run from a chrome:// URL. There used to be a way for a regular web page (served from http://) to request extended capabilities (called UniversalXPConnect) but it has been removed out of security concerns.
I think you should tell us a little more about what it is you're trying to achieve. If you're trying to export data from your addon into a webpage, the AddonSDK (see https://addons.mozilla.org/en-US/developers/docs/sdk/latest/dev-guide/) has a very good protocol for doing that called page-mod; it allows you to inject data into web pages.
Thanks to Jonathan's advice I was able to come up with a great solution to this problem. Here is the code I'm using:
main.js:
var data = require("self").data;
var pageMod = require("page-mod");
const {Cc,Ci} = require("chrome");
pageMod.PageMod({
include: "*",
contentScriptFile: data.url("copy-helper.js"),
onAttach: function(worker) {
worker.port.on("handleCopy", function(copyInfo) {
var gClipboardHelper = Cc["#mozilla.org/widget/clipboardhelper;1"].getService(Ci.nsIClipboardHelper);
gClipboardHelper.copyString(copyInfo.dataToCopy);
});
}
});
copy-helper.js:
window.addEventListener("copyEvent", function (event) {
self.port.emit('handleCopy', event.detail.copyInfo);
}, false);
in my apps javascript
var event = new CustomEvent("copyEvent", {
detail:{
copyInfo: {dataToCopy:"my string"}
}
});
window.dispatchEvent(event);
Hope this helps anyone else who's ran into this issue!