So, I tried to load my add-on using the about:debugging page in Firefox. But, it simply wouldn't load. Is there somewhere where an error would be logged that I could find it?
Here is my manifest.JSON code:
{
"description": "Adds a stickfigure",
"manifest_version": 2,
"name": "StickMan",
"version": "1.0",
"icons": {
"48": "icons/StickMan-48.png"
},
"applications": {
"gecko": {
"id": "extention#stick.man",
"strict_min_version": "45.0"
}
},
"permissions": [
"activeTab"
],
"background": {
"scripts": ["StickManUpdate.js"]
},
"browser_action": {
"default_icon": {
"48": "icons/StickManButton.png"
},
"default_title": "Call StickMan",
},
}
I hope that this helps other frustrated add-on creators.
Thanks in advance
The lack of loading issue is that you have multiple syntax errors in the JSON of your manifest.json file. In your manifest.json file the lines at the end of the file:
"default_title": "Call StickMan",
},
}
Should not have the extra , (which would indicate you are going to have another property in the Object):
"default_title": "Call StickMan"
}
}
If you were using the Firefox Developer Edition, the fact that you had these errors would have been obvious:
However, even if you are running Firefox 47.0.1 and had merely used the Browser Console (keyboard shortcut: Ctrl-Shift-J), as suggested in the comments, you would have seen the error:
A promise chain failed to handle a rejection. Did you forget to '.catch', or did you forget to 'return'?
See https://developer.mozilla.org/Mozilla/JavaScript_code_modules/Promise.jsm/Promise
Date: Sun Jul 17 2016 11:11:22 GMT-0700 (Pacific Standard Time)
Full Message: SyntaxError: JSON.parse: expected double-quoted property name at line 33 column 2 of the JSON data
Full Stack: readJSON/</<#resource://gre/modules/Extension.jsm:628:19
NetUtil_asyncFetch/<.onStopRequest#resource://gre/modules/NetUtil.jsm:128:17
While a bit cryptic, it still shows the line number of the first issue:
Full Message: SyntaxError: JSON.parse: expected double-quoted property name at line 33 column 2 of the JSON data
The error produced in the Browser Console of Firefox Developer Edition is a bit easier to parse as to what the issue is:
SyntaxError: JSON.parse: expected double-quoted property name at line 33 column 2 of the JSON data
Stack trace:
readJSON/</<#resource://gre/modules/Extension.jsm:859:19
NetUtil_asyncFetch/<.onStopRequest#resource://gre/modules/NetUtil.jsm:128:17
WebExtensions Development:
The WebExtensions API is currently in development. If you are developing a WebExtension, you should be using either Firefox Nightly, or Firefox Developer Edition in order to test your code.
More on your code:
Syntax error:
In addition to the above syntax errors, you have more issues. I did not attempt to resolve all of them, but did get sucked into fixing enough so that the add-on was functional. The next reported error, a syntax error, is in your StickManUpdate.js file on the code:
browser.tabs.sendMessage(
message: "End";
);
You have multiple issues here. Please see the tabs.sendMessage() documentation. You are missing the required tabId parameter. In addition, you appear to be mixing-up the difference between having an Object being passed as a parameter containing properties which are the information passed to the method versus a list of parameters which are other native types passed to a method. Note: It is not uncommon for there to be both a list of parameters of various native or non-native types and an Object containing properties which are data passed to the method.
Assuming browserAction is defined:
You use methods of browserAction in multiple locations where it should be browser.browserAction. browserAction by itself is not defined. Alternately, you could use browserAction as a shortcut by defining it like: var browserAction = browser.browserAction;.
Use of browserAction.getTitle() as if it is synchronous when in reality it is asynchronous:
You make a call to browserAction.getTitle() to get the value of the title. The value of the title is only available in the callback function, which you do not supply. This implies a lack of understanding of asynchronous programming. You might want to review some questions on that subject like:
Why isn't a global variable set immediately after defining a callback/listener function (asynchronous messaging, port.on)
Why is my variable unaltered after I modify it inside of a function? - Asynchronous code reference
How do I return the response from an asynchronous call?
Wrong parameter type supplied to browserAction.setTitle():
This appears to, again, be confusion as to the difference between parameters of other native types and a parameter that is an Object (which may be an Object literal) which contains properties which are the information passed to the method. Admittedly, WebExtensions appear to almost arbitrarily mix using actual parameters and Objects with the properties functioning as parameters when passing information to methods. It appears that being careful as to which is being used in a particular method will be required.
Not having various functions specify the ID for the tab:
In multiple calls to various methods, you do not pass the tabId when you should. You are adding your StickMan canvas to a single tab per mouse click. You should be passing the tab ID for calls to multiple methods.
Assigning to document.body.innerHTML in stickman.js:
In general, assigning to innerHTML at any time should be avoided, if possible. It is a bad idea under most circumstances. In most instances, it may cause the entire DOM to be re-evaluated. For doing what you desire, adding HTML in text format to the DOM at the end of the HTML for an element, there is a specific function which is better/faster: insertAdjacentHTML(). Your code:
document.body.innerHTML+= '<canvas id="StickManCanvas0000000" width="100" height="200"></canvas>';
Could be written as:
document.body.insertAdjacentHTML("beforeend", '<canvas id="StickManCanvas0000000" width="100" height="200"></canvas>');
However, it is still a bad idea to use insertAdjacentHTML() here. There is a significant stigma attached to using either insertAdjacentHTML() or assigning to innerHTML. Using either will result in your add-on receiving additional scrutiny when submitted to AMO for distribution. This is mostly because there are real security issues with using either methodology for changing the DOM. The security issues are when what is being added is text that is dynamically generated from input/data which is not hard coded into your add-on. In addition, you are already mixing adding the element as text and performing changes to it using other JavaScript (e.g. assigning to canvas.style.position). You really should use one or the other. In this case, it is better to construct canvas entirely in JavaScript. It is, after all, only 4 lines to do the same thing you were doing in the two you were using for the innerHTML assignment and the getElementById() to find the canvas element.
Personally, I like using insertAdjacentHTML() in many instances with complex structures. It is generally faster to use it for inserting larget amounts of HTML. It also allows you to keep what is being inserted represented as text. Such text may be much easier to visualize the structure being added rather than figuring out what a large chunk of DOM generated using document.createElement() and setAttribute() actually looks like. However, along with the other drawbacks mentioned above, using insertAdjacentHTML() may not lend itself as easily to writing modular code.
Issues with how you insert you content script and canvas:
Every time the user clicks on your browserAction button you insert another copy of your content script into the tab. This leads to issues of errors being generated due to the consumed content scripts getting the message sent by your call to browser.tabs.sendMessage() and not being able to find the canvas. The correct solution to this is to only chrome.tabs.executeScript() the first time the button is clicked in a tab and then send a message to the content script each subsequent time the button is clicked in that tab causing the same canvas to be re-inserted into the DOM. An easy way to track if you have already loaded the StickMan into a particular tab is to use setTitle() to have the title for your button be different after the first run in that tab.
Other issues:
Note: Your code structure in stickman.js is a bit convoluted. You might want to address this.
All together
manifest.json:
{
"description": "Adds a stickfigure",
"manifest_version": 2,
"name": "StickMan",
"version": "1.0",
"icons": {
"48": "icons/StickMan-48.png"
},
"applications": {
"gecko": {
"id": "extention#stick.man",
"strict_min_version": "45.0"
}
},
"permissions": [
"activeTab"
],
"background": {
"scripts": ["StickManUpdate.js"]
},
"browser_action": {
"default_icon": {
"48": "icons/StickManButton.png"
},
"default_title": "Call StickMan",
"browser_style": true
}
}
StickManUpdate.js:
browser.browserAction.onClicked.addListener(function(tab) {
browser.browserAction.getTitle({tabId:tab.id},function(title){
if(title === 'Call StickMan') {
chrome.tabs.executeScript(tab.id, {
file: "/content_scripts/stickman.js"
});
browser.browserAction.setTitle({title:'Recall StickMan',tabId:tab.id});
} else if (title === 'Call StickMan again') {
browser.tabs.sendMessage(tab.id,"Draw");
browser.browserAction.setTitle({title:'Recall StickMan',tabId:tab.id});
}else {
browser.tabs.sendMessage(tab.id,"End");
browser.browserAction.setTitle({title:'Call StickMan again',tabId:tab.id});
}
});
});
stickman.js:
var running = true;
//document.body.insertAdjacentHTML("beforeend", '<canvas id="StickManCanvas0000000" width="100" height="200"></canvas>');
var canvas = document.createElement("canvas");
canvas.setAttribute("width",100);
canvas.setAttribute("height",200);
//var canvas = document.getElementById('StickManCanvas0000000');
canvas.style.position = 'fixed';
canvas.style.left = '0px';
canvas.style.top = (window.innerHeight-200)+'px';
canvas.style.backgroundColor = 'rgba(0, 0, 0, 0)';
canvas.style.border = '1px dashed red';
var ctx = canvas.getContext('2d');
var pos = {
x:0,
headX:50,
headY:20,
bodyX:50,
bodyY:150,
leftArmX:25,
leftArmY:90,
rightArmX:75,
rightArmY:90,
leftLegX:30,
leftLegY:200,
rightLegX:70,
rightLegY:200,
};
var setPos = function(x, y) {
canvas.style.left = x+'px';
canvas.style.top = (window.innerHeight-y-200)+'px';
};
var drawMan = function(time) {
setPos(pos.x, 0);
ctx.strokeStyle = '#000000';
ctx.lineWidth = 5;
ctx.beginPath();
ctx.arc(pos.headX, pos.headY, 20, 0, Math.PI*2, false);
ctx.moveTo(pos.headX, pos.headY);
ctx.lineTo(pos.bodyX, pos.bodyY);
ctx.lineTo(pos.rightLegX, pos.rightLegY);
ctx.moveTo(pos.bodyX, pos.bodyY);
ctx.lineTo(pos.leftLegX, pos.leftLegY);
ctx.moveTo((pos.bodyX+pos.headX)/2, ((pos.bodyY+pos.headY)/5)*2);
ctx.lineTo(pos.rightArmX, pos.rightArmY);
ctx.moveTo((pos.bodyX+pos.headX)/2, ((pos.bodyY+pos.headY)/5)*2);
ctx.lineTo(pos.leftArmX, pos.leftArmY);
ctx.stroke();
ctx.fillStyle = '#888888';
ctx.beginPath();
ctx.arc(pos.headX, pos.headY, 20, 0, Math.PI*2, false);
ctx.fill();
if(running) {
window.requestAnimationFrame(drawMan);
}
};
drawMan();
document.body.appendChild(canvas);
browser.runtime.onMessage.addListener(function(m) {
if(m === 'End' && running === true) {
running = false;
document.body.removeChild(canvas);
} else if(m === 'Draw' && running === false) {
running = true;
document.body.appendChild(canvas);
}
});
Functionality demo [Note1: You must navigate to an actual webpage. Note2: The tooltips that pop up to tell you what the title is of your browser_action button are not captured with the program I used to create the following .gif. Note3: I added the browser_style property to the browser_action in your manifest.json file. It is new in Firefox 48. Without it, Firefox will issue a warning in the Browser Console when the add-on is loaded.]:
Related
When using the Lego... I mean Block Kit builder, I need to send some pre-formatted data using markdown's "```" syntax, the problem is when it encounters a long un-spaced strings (such as a URL) the format breaks when displaying on slack. This is isolated to the block kit builder as sending the message through the app/web app shows up fine.
I tried using the verbatim property, however that did not work.
First block displays the error, second block displays correctly due to spaces being present and no long-words
[
{
"type": "section",
"text": {
"type": "mrkdwn",
"text": "```I_love_oat_cake_croissant_jujubes_tiramisu_pudding_pastry_sugar_plum_I_love._Apple_pie_powder_bear_claw_croissant_candy_muffin_gummi_bears.```"
}
},
{
"type": "section",
"text": {
"type": "mrkdwn",
"text": "```I love oat cake croissant jujubes tiramisu pudding pastry sugar plum I love. Apple pie powder bear claw croissant candy muffin gummi bears.```"
}
}
]
I expected it to mimic what a user's input would display when entering data on the app, which is breaking on long words when necessary, but instead the block is getting shifted off to the left and cutting off some of the data.
Here is an example in both the builder and the application
Currently as of 4/15: This is a known bug so all I can do is wait. They do not offer a public facing site that lists known issues like this, but I have emailed them to track the bug and hopefully if it gets fixed soon I will update this answer.
The only options I can find that deal with custom coloring inside Visual Studio Code are those:
"editor.tokenColorCustomizations": {
"[Atom One Dark]": {
"comments": "#FF0000" // only 6 color-customizable
},
"workbench.colorCustomizations": {
"statusBar.background": "#666666",
"panel.background": "#555555",
"sideBar.background": "#444444"
},
},
Is there a way to set a custom color for these types of things? One exists for comments, for strings (inside "string"). I believe something similar should be possible for other stuff using regexps. Thanks in advance.
Search for tokenColorCustomizations and textMateRules and you will find that you can do something like this (see colorizing with textmate):
"editor.tokenColorCustomizations": {
"textMateRules": [
{
// works for a language that recognizes backticks as string templates
"scope": "string.template",
"settings": {
"foreground": "#FF0000"
}
},
{
"scope": "punctuation.definition.template-expression.begin, punctuation.definition.template-expression.end",
"settings": {
"foreground": "#F0F"
}
},
]
}
using the Inspect TM Scopes... command in the command palette. So I used that command to click inside a template literal and got the string.template.js.jsx scope. But that didn't change the ${someVar} so I inspected that scope and saw variable.other.readwrite.js.jsx plus additional scope parameters - still working on isolating that kind of a variable from other variables.
Edit: Should work in more file types by using only string.template, if the language treats backticks as a string template.
You can also change the fontStyle within a setting. There are a few answers here about using the TM Scopes command.
I need to do some post-processing work on a png file of a Highchart graph. How do I determine when the export is finished? I've tried to attach a function, but it never gets called:
console.log("Saving chart...");
chart.exportChart({
type : "application/png",
filename: "tmp_chart_filename"
},
function(data) {
console.log("Export done, Data: " + data); // Not called.
})
console.log("Out");
To my understanding, it is not possible out of the box.
What happens internally in the exportChart() method is, a form is created on the fly and the chart svg is sent to the server by programmatically triggering a submit on this form. The server in turn, processes received svg into a png (or whatever you may select) and returns it to the browser.
The popup you see that asks you to "save as" is the action of the browser (and not any highchart code) when a file is thrown at it. Basically the returned png is never returned to the code, it goes directly to the browser.
You can however write your custom svg->png server module and do your magic there :)
I had a rather similar issue and solved it by defining the onclick event on the contextButton. This seems possible only if you are OK with losing the items in the context menu (export by file type), which wasn't an issue in my case. Below the code to be included in the chart initial building:
[... your Highcharts chart setup ...],
exporting: {
buttons: {
contextButton: {
menuItem: null, // You'll lose your menu items here
onclick: function(event) {
yourFunctionBeforeExport();
this.exportChart();
yourFunctionAfterExport();
}
}
}
}
[... rest of the Highcharts chart setup ...]
As I was writing a Firefox add-on using the Add-on SDK, I noticed that the add-on code and the content script code block the execution of each other. Furthermore, the add-on code seems even to block the interaction with other Firefox windows (not just tabs).
What is the concurrency/process model of Firefox add-ons?
Is it possible to run add-on code and content script code concurrently without cooperative multithreading (a la timers)?
How many times is the add-on code loaded? Once per window? Once per tab? Once?
The documentation states:
The Mozilla platform is moving towards a model in which it uses
separate processes to display the UI, handle web content, and execute
add-ons. The main add-on code will run in the add-on process and will
not have direct access to any web content.
So I hope that in the future that they are indeed separate processes that will not interfere with each other, but that doesn't seem to be the case now.
Update:
I have tried using a page-worker from the add-on code, but unfortunately that still blocks the content script (as well as all other javascript). I also tried using a web worker in the page-worker, but I get the following error when calling the web worker's postMessage function.
TypeError: worker.postMessage is not a function
I also tried creating an iframe in the page-worker and then creating a web worker in the iframe, but unfortunately I cannot use window.addEventListener from the page-worker. I get the following error:
TypeError: window.addEventMessage is not a function
Finally, I tried to inject script (via script element) into the page-worker page to create a web worker which does seem to work. Unfortunately, I cannot communicate with this web worker because I can only send messages to it via document.defaultView.postMessage.
Oh the tangled webs I am weaving...
content-script -> add-on -> page-worker -> iframe -> web worker -> my code
I have included a simple example:
package.json
{
"name": "test",
"author": "me",
"version": "0.1",
"fullName": "My Test Extension",
"homepage": "http://example.com",
"id": "jid1-FmgBxScAABzB2g",
"description": "My test extension"
}
lib/main.js
var data = require("self").data;
var pageMod = require("page-mod");
pageMod.PageMod({
include: ["http://*", "https://*"],
contentScriptWhen: "start",
contentScriptFile: [data.url("content.js")],
onAttach: function (worker) {
worker.port.on("message", function (data) {
// simulate an expensive operation with a busy loop
var start = new Date();
while (new Date() - start < data.time);
worker.port.emit("message", { text: 'done!' });
});
}
});
data/content.js
self.port.on("message", function (response) {
alert(response.text);
});
// call a very expensive operation in the add-on code
self.port.emit("message", { time: 10000 });
The messaging system has been designed with a multi-process environment in mind. However, this environment didn't emerge and it looks like it won't happen in near future either. So what you really have is both the add-on and the content script running in the same process on the main thread (UI thread). And that means that only one of them is running at a time, as you already noticed there is no concurrency.
Is it possible to run add-on code and content script code concurrently without cooperative multithreading (a la timers)?
Yes, you use web workers (that have nothing to do with the page-worker module despite a similar name). This would be generally recommendable for expensive operations - you don't want your add-on to stop responding to messages while it is doing something. Unfortunately, the Add-on SDK doesn't expose web workers properly so I had to use the work-around suggested here:
worker.port.on("message", function (message) {
// Get the worker class from a JavaScript module and unload it immediately
var {Cu} = require("chrome");
var {Worker} = Cu.import(data.url("dummy.jsm"));
Cu.unload(data.url("dummy.jsm"));
var webWorker = new Worker(data.url("expensiveOperation.js"));
webWorker.addEventListener("message", function(event)
{
if (event.data == "done")
worker.port.emit("message", { text: 'done!' });
}, false);
});
The JavaScript module data/dummy.jsm only contains a single line:
var EXPORTED_SYMBOLS=["Worker"];
How many times is the add-on code loaded? Once per window? Once per tab? Once?
If you are asking about add-on code: it is loaded only once and stays around as long as the add-on is active. As to content scripts, there is a separate instance for each document where the script is injected.
I found a hack to get WebWorkers in the extension's background page:
if(typeof(Worker) == 'undefined')
{
var chromewin = win_util.getMostRecentBrowserWindow();
var Worker = chromewin.Worker;
}
var worker = new Worker(data.url('path/to/script.js'));
By accessing the main window's window object, you can pull the Worker class into the current scope. This gets around all the obnoxious Page.Worker workaround junk and seems to work fairly well.
$("div.square").droppable({
accept: '.white',
drop: function (event, ui)
{
$to = "#" + $(this).attr('id');
alert(to);
$.post(
"/Game/AddMove",
{
from: $from,
to: $to,
GameID: $("#gameID").val()
});
}
});
Well it's nor working. So I must ask, is it possible to call AJAX on droping some UI element ?
The problem is, it's not even calling an controller,
I'm going to guess that you want your to variable to be the id attr of the dropped element. I have no idea where you intended the value of $from to come from.
Just a side note - I would suggest not using variables starting with a $, especially not with jQuery.
Anyway, to access the object that dropped, do this:
drop: function(event, ui) {
toStr = '#' + $(ui.helper).attr('id');
}
in other words, ui.helper is the HTML object that was dropped onto your droppable.
Good luck.
Yes, you can trigger ajax calls on drop. (live example)
I see a couple of issues (details below):
$("div.square").droppable({
accept: '.white',
drop: function (event, ui)
{
$to = "#" + $(this).attr('id');// <== 1. Implicit Global?
alert(to); // <== 2. Syntax error
$.post(
"/Game/AddMove",
{
from: $from, // <== 3. Where does this come from?
to: $to,
GameID: $("#gameID").val()
});
}
});
You seem to be using $to without declaring it. (If it's declared in a containing scope you haven't shown, that's fine; otherwise, though, you're falling prey to the Horror of Implicit Globals. (That's probably not what's keeping it from working, though.)
The code should blow up here, unless to is defined somewhere. You don't have any variable called to.
You're using a variable $from that isn't defined in your quoted code. Fair 'nuff if it's defined in an enclosing scope, but if so, probably worth mentioning in the question.
If none of the above is it, just walk through the code. There are plenty of browser-based debuggers these days. If you use Firefox, install and use Firebug. If you use Chrome or Safari, they have Dev Tools built in (you may have to enable them in preferences). If you use IE, IE8 and up have a built-in debugger; for IE7 and earlier you can use the free version of VS.Net. For Opera, it has a built-in debugger called Dragonfly... In all of these, you can set a breakpoint on the first line of your drop handler and step through to see what's going wrong.