C++ static archive library with multiple definition conflicts in debug - javascript
I'm writing a mobile app with c++ using marmalade middleware software, my plugin I'm making has a component that relies on a compiled python library.
my application works with this 2.6 version:
https://github.com/marmalade/python
switched to 2.7 with socket support:
https://github.com/guyburton/python-loves-marmalade
My first step is I compile my library for the right platform, it is then linked into my application> this creates a file libpython.a ( debug libpython_d.a )
My application has some c-python functions so I also link the required python.h and pyrun.h headers.
none of my headers make reference to python.h or python.c And wmApplication.cpp doesnt make reference to python at all, it does call my plugin though.
1> ARM Compiling(GCC) c:\marmalade\7.3\web\wmapplication\wmApplication.cpp ...
1> ARM Compiling(GCC) c:\TestMove\fluidSec\pyRun.cpp ...
1> ARM Linking(GCC) ...
1> c:/TestMove/python-master/lib/arm\libpython.a(python.obj): In function `main':
1> python.c:(.text.startup.main+0x0): multiple definition of `main'
1> :Release_fluidSec_vc12x_gcc_arm/wmApplication.obj(wmApplication.cpp) : first defined here (col (.text.startup.main+0x0))
I had my plugin working when I was on python version 2.6 then I switched to a different implementation of python 2.7
Isn't
" multiple definition of `main' "only an issue when you have two main functions with the same function signature??? meaning they both have the same program structure? As I understand this can happen regardless of the actual function name being main, the compiler switches it to main as compile time.
I don't have any other instances of python.c ( I searched )
Also: if i compile with the debug version of the library, the error appears to keep the old path in which the library was built ( on a network drive Z:.. even after I disconnect ), if I open the library in a hex editor the path is embedded in the debug info... any change this could be an issue?
1> ARM Compiling(GCC) c:\marmalade\7.3\web\wmapplication\wmApplication.cpp ...
1> ARM Compiling(GCC) c:\TestMove\fluidSec\pyRun.cpp ...
1> ARM Linking(GCC) ...
1> c:/TestMove/python-master/lib/arm\libpython_d.a(python.obj): In function `main':
1> z:/_ProjectFolder/python-master/modified/Modules/python.c:11: multiple definition of `main'
1> :Debug_fluidSec_vc12x_gcc_arm/wmApplication.obj(wmApplication.cpp) : first defined here (col (.text.main+0x0))
Any ideas? I'm clearly missing something!
( definitely cleaned all temp folders and restarted, hoping for a cache issue )
edit:
here is python.c
/* Minimal main program -- everything is loaded from the library */
#include "Python.h"
#ifdef __FreeBSD__
#include <floatingpoint.h>
#endif
int
main(int argc, char **argv)
{
/* 754 requires that FP exceptions run in "no stop" mode by default,
* and until C vendors implement C99's ways to control FP exceptions,
* Python requires non-stop mode. Alas, some platforms enable FP
* exceptions by default. Here we disable them.
*/
#ifdef __FreeBSD__
fp_except_t m;
m = fpgetmask();
fpsetmask(m & ~FP_X_OFL);
#endif
return Py_Main(argc, argv);
}
wmApplication.c
/*
* (C) 2001-2012 Marmalade. All Rights Reserved.
*
* This document is protected by copyright, and contains information
* proprietary to Marmalade.
*
* This file consists of source code released by Marmalade under
* the terms of the accompanying End User License Agreement (EULA).
* Please do not use this program/source code before you have read the
* EULA and have agreed to be bound by its terms.
*/
#include <string>
#include "s3eKeyboard.h"
#include "s3eConfig.h"
#include "s3eWebView.h"
#include "IwDebug.h"
#include "IwGx.h"
#include "../IwWMDispatcher.h"
#include "FallbackPage.h"
#include "../IwWMRegisterModules.h"
#define WEB_DEFAULT_ROOT_DIRECTORY "webassets"
#define WEB_DEFAULT_URL "index.html"
//TOOD Wide char support (relevant for the next line)??
#define ALLOCATION_SIZE_FOR_CONFIG_STRINGS sizeof(char) * S3E_CONFIG_STRING_MAX
#define WEB_DEFAULT_SCHEME LOCAL_ROM_URL
#define URL_HAS_SCHEME(x) strstr(x, "://")
typedef enum CurrentURLType
{
STARTUP_URL_USER_DEFINED,
STARTUP_URL_DEFAULT,
STARTUP_URL_EMBEDDED
} CurrentURLType;
//TODO Implement these globals in a better way (there's no point yet as we may wildly change
//where all out defaults are coming from)
// Tells us which url to try and navigate to (no ownership)
static const char* const * g_WebStartupURL = NULL;
// Scheme to locate local files (might one day not be const)
static const char* const g_LocalFileURLScheme = WEB_DEFAULT_SCHEME;
// Root folder of all web app assets
static char* g_WebAssetsRootDirectory = NULL;
// User defined start URL from icf
static char* g_WebUserDefinedURL = NULL;
// Default start URL if user didn't specify one
static char* g_WebDefaultURL = NULL;
// State representing which stage of fallbacks we're in
static CurrentURLType g_CurrentURLType = STARTUP_URL_USER_DEFINED;
// Name of temp file to create and load as our final fallback url
static const char* g_WebInternalFallbackURL = LOCAL_RAM_URL "error.html";
// Flag to indicate whether we need to cleanup a tempfile
static bool g_DeleteTempURL = false;
static bool g_UsingWinSim = false;
// The main webview
static s3eWebView* g_WebView = NULL;
// The Javascript Dispatcher
static IwWebMarmalade::CDispatcher* g_Dispatcher = NULL;
// Fwd delcarations
bool CreateFallbackPage(const char* url, const char* content);
void DeleteFallbackPage(const char* url);
//Assumes that str is long enough to take the prefix
static void strprepend(const char* prefix, char* str)
{
const int originalLen = strlen(str);
const int prefixLen = strlen(prefix);
//Shift the string along
memmove(str+prefixLen, str, originalLen);
//Can use memcpy for the prefix
memcpy(str, prefix, prefixLen);
//NULL terminate
str[originalLen+prefixLen] = '\0';
}
static bool InitGlobals()
{
if (!g_WebAssetsRootDirectory)
{
if (!(g_WebAssetsRootDirectory = (char*)s3eMalloc(ALLOCATION_SIZE_FOR_CONFIG_STRINGS + 1))) // + 1 for possible extra "/"
return false; //Like it's going to happen at this stage!
if (s3eConfigGetString("Web", "WebRootDirectory", g_WebAssetsRootDirectory) != S3E_RESULT_SUCCESS)
{
strcpy(g_WebAssetsRootDirectory, WEB_DEFAULT_ROOT_DIRECTORY);
}
int length = strlen(g_WebAssetsRootDirectory);
if (g_WebAssetsRootDirectory[length - 1] != '/' || g_WebAssetsRootDirectory[length - 1] != '\\') //TODO Better way?
{
g_WebAssetsRootDirectory[length] = '/';
g_WebAssetsRootDirectory[length + 1] = 0;
}
//Root directory mustn't contain a scheme
if (URL_HAS_SCHEME(g_WebAssetsRootDirectory))
{
IwTrace(WEBMARMALADE, ("WebRootDirectory must not contain a scheme"));
return false;
}
}
if (!g_WebUserDefinedURL)
{
const int totalCapacity = ALLOCATION_SIZE_FOR_CONFIG_STRINGS +
strlen(g_WebAssetsRootDirectory) + strlen(g_LocalFileURLScheme);
if (!(g_WebUserDefinedURL = (char*)s3eMalloc(totalCapacity)))
return false; //Like it's going to happen at this stage!
if (s3eConfigGetString("Web", "WebStartURL", g_WebUserDefinedURL) != S3E_RESULT_SUCCESS)
{
//No user defined URL
g_CurrentURLType = STARTUP_URL_DEFAULT;
g_WebUserDefinedURL[0] = 0;
}
//TODO This means if the user specifies e.g. www.madewithmarmalade.com then this fails.
//We should possibly check for things like "www." as well.
else if (!URL_HAS_SCHEME(g_WebUserDefinedURL))
{
//The user defined url is "relative" then add the scheme and the default file location
strprepend(g_WebAssetsRootDirectory, g_WebUserDefinedURL);
strprepend(g_LocalFileURLScheme, g_WebUserDefinedURL);
}
}
if (!g_WebDefaultURL)
{
const int totalCapacity = strlen(WEB_DEFAULT_URL) +
strlen(g_WebAssetsRootDirectory) + strlen(g_LocalFileURLScheme);
if (!(g_WebDefaultURL = (char*)s3eMalloc(totalCapacity)))
return false; //Like it's going to happen at this stage!
strcpy(g_WebDefaultURL, WEB_DEFAULT_URL);
if (!URL_HAS_SCHEME(g_WebDefaultURL))
{
//The default url is "relative" then add the scheme and the default file location
strprepend(g_WebAssetsRootDirectory, g_WebDefaultURL);
strprepend(g_LocalFileURLScheme, g_WebDefaultURL);
}
}
if (g_CurrentURLType == STARTUP_URL_USER_DEFINED)
g_WebStartupURL = &g_WebUserDefinedURL;
else if (g_CurrentURLType == STARTUP_URL_DEFAULT)
g_WebStartupURL = &g_WebDefaultURL;
g_UsingWinSim = s3eDeviceGetInt(S3E_DEVICE_OS) == S3E_OS_ID_WINDOWS;
IwTrace(WMAPP, ("xxxxxxxxxxxxxxxxxxxxxx: (userdefined) %s", g_WebUserDefinedURL));
return true;
}
static bool CheckQuit()
{
bool rtn = s3eDeviceCheckQuitRequest()
|| (s3eKeyboardGetState(s3eKeyEsc) & S3E_KEY_STATE_PRESSED)
|| (s3eKeyboardGetState(s3eKeyAbsBSK) & S3E_KEY_STATE_PRESSED);
if (rtn)
IwTrace(WEBMARMALADE, ("Quitting Web Marmalade App"));
return rtn;
}
static int32 handleReset(void* systemData, void* userData)
{
s3eWebViewNavigate(g_WebView, *g_WebStartupURL);
return 1;
}
static void Terminate()
{
s3eDeviceUnRegister(S3E_DEVICE_SIMULATOR_RESTART, handleReset);
if (g_Dispatcher)
{
delete g_Dispatcher;
g_Dispatcher = NULL;
}
if (g_WebView)
{
s3eWebViewDestroy(g_WebView);
g_WebView = NULL;
//Free the strings
free(g_WebAssetsRootDirectory);
free(g_WebDefaultURL);
free(g_WebUserDefinedURL);
g_WebAssetsRootDirectory = NULL;
g_WebDefaultURL = NULL;
g_WebUserDefinedURL = NULL;
if (g_DeleteTempURL)
DeleteFallbackPage(g_WebInternalFallbackURL);
}
}
void DoNavigate()
{
IwTrace(WEBMARMALADE, ("loading url = %s", *g_WebStartupURL));
s3eWebViewNavigate(g_WebView, *g_WebStartupURL);
}
static int32 screenResizePending(void* systemData, void* userData)
{
IwTrace(WEBMARMALADE, ("screenResize Pending, calling s3eSurfaceShow()"));
// First call to surface show resizes the surface and calls the S3E_SURFACE_SCREENSIZE
// callback. No surface is actually displayed
s3eSurfaceShow();
// On ios the first call only sets the state to resize pending. The second call actually performs it
s3eSurfaceShow();
return 0;
}
static int32 screenSizeChanged(void* systemData, void* userData)
{
IwTrace(WEBMARMALADE, ("screenSizeChanged callback fired. Resizing webview"));
s3eWebViewResize(g_WebView, 0, 0, s3eSurfaceGetInt(S3E_SURFACE_WIDTH), s3eSurfaceGetInt(S3E_SURFACE_HEIGHT));
// This is annoying. If we don't do this on android then the OS locks the surface and
// we the screen doesn't even rotate natively.
s3eSurfaceShow();
return 0;
}
static int32 pageNotFound(s3eWebView *instance, void *systemData, void *userData)
{
switch (g_CurrentURLType)
{
case STARTUP_URL_USER_DEFINED:
g_CurrentURLType = STARTUP_URL_DEFAULT;
IwTrace(WEBMARMALADE, ("pageNotFound: trying default url = %s", g_WebDefaultURL));
g_WebStartupURL = &g_WebDefaultURL;
break;
case STARTUP_URL_DEFAULT:
g_CurrentURLType = STARTUP_URL_EMBEDDED;
IwTrace(WEBMARMALADE, ("pageNotFound: creating and loading fallback url = %s", g_WebInternalFallbackURL));
if (!(g_DeleteTempURL = CreateFallbackPage(g_WebInternalFallbackURL, g_WebInteralFallbackContent)))
{
IwTrace(WEBMARMALADE, ("failed to create fallback url: quitting"));
s3eDeviceRequestQuit();
return 0;
}
else
{
g_WebStartupURL = &g_WebInternalFallbackURL;
}
break;
default:
IwTrace(WEBMARMALADE, ("pageNotFound: can't find loadable page, quitting"));
s3eDeviceRequestQuit();
return 0;
}
DoNavigate();
return 0;
}
bool Init()
{
// Clear the debug screen from behind the app (you can see it on iOS when you drag
// the browser).
s3eSurfaceClear(0, 0, 0);
s3eSurfaceShow();
if (!s3eWebViewAvailable())
{
IwError(("Webview not available"));
return false;
}
InitGlobals();
if (!(g_WebView = s3eWebViewCreate()))
{
IwTrace(WEBMARMALADE, ("Failed to create webview"));
return false;
}
g_Dispatcher = new IwWebMarmalade::CDispatcher(g_WebView);
if (!g_Dispatcher)
{
IwTrace(WEBMARMALADE, ("Failed to create dispatcher"));
return false;
}
s3eWebViewRegister(S3E_WEBVIEW_FAILED_LOADING, pageNotFound, 0, g_WebView);
s3eSurfaceRegister(S3E_SURFACE_SCREENSIZE, screenSizeChanged, NULL);
s3eDeviceRegister(S3E_DEVICE_SIMULATOR_RESTART, handleReset, NULL);
s3eSurfaceRegister((s3eSurfaceCallback)2, screenResizePending, NULL);
s3eWebViewShow(g_WebView, 0, 0, s3eSurfaceGetInt(S3E_SURFACE_WIDTH), s3eSurfaceGetInt(S3E_SURFACE_HEIGHT));
// For some iOS devices we need to call this on init otherwise the webview is not displayed
s3eSurfaceShow();
DoNavigate();
return true;
}
bool Update()
{
// Work-around since socket events do not unyield the app in windows
if (!g_UsingWinSim)
s3eDeviceYieldUntilEvent();
else
s3eDeviceYield(0);
s3eKeyboardUpdate();
return true;
}
int main()
{
IwTrace(WEBMARMALADE, ("Started Web Marmalade App"));
if (!Init())
return 0;
//Main loop
while (!CheckQuit())
{
if (!Update())
break;
}
Terminate();
return 0;
}
I figured out why it was working before and now it wasn't,
The marmalade MKB file ( which sets up visual studio for cross compiling) had python.c referenced in it to include when it wasn't necessary.
for those wanting to do the same, python in web marmalade, i'll be linking my framework on github as i've made numerous changes to the python implementations, just a few minor issues ( that took me way too long to solve)
Related
JINT performance is significantly worse when an application is run as a 64bit application
The application requires JS support (the program is written in C#) and JINT is being used as the interpreter. I have noticed how performance significantly degrades when the application is run as a 64bit application. I have simplified this down to the following example which illustrates the issue: Stopwatch sw = new Stopwatch(); sw.Start(); for (int i = 0; i < 1000; i++) { var engine = new Jint.Engine(cfg => { cfg.Strict(true); // Good practive for javascript cfg.AllowClr(); // Access to .net cfg.LimitRecursion(16); // Help stop broken scripts taking down application cfg.CatchClrExceptions(ex => ex is Exception); }); try { engine.Execute(#" function test() { throw 'Error'; }; test(); "); } catch (Exception) { } } sw.Stop(); Debug.WriteLine(sw.Elapsed); When this is compiled as a 32bit application it takes around 11 seconds. When this is compiled as a 64bit application is takes around 35 seconds. Please that the exceptions are thrown quite frequently in the real application. Does anyone know why this is the case? Note this only appears to be an issue when running under the debugger. Outside the debugger the performance seems to be similar. Update #1 I have still been working on this and now have this example: public partial class Form1 : Form { public Form1() { InitializeComponent(); Stopwatch sw = new Stopwatch(); sw.Start(); for (int i = 0; i < 20000; i++) { DefinedDotNetApi(); } sw.Stop(); MessageBox.Show(sw.Elapsed.ToString()); } public static void DefinedDotNetApi() { var engine = new Jint.Engine(); engine.SetValue("demoJSApi", new DemoJavascriptApi()); var result = engine.Execute("demoJSApi.helloWorldFromDotNet('TestTest');demoJSApi.helloWorldFromDotNet('TestTest')").GetCompletionValue(); } public class DemoJavascriptApi { public string helloWorldFromDotNet(string name) { return $"Hello {name} - this is executed in {typeof(Program).FullName}"; } } } When runs a 32bit app this is 20-30% faster than when built as a 64bit app. Even doing this: public static void DefinedDotNetApi() { var engine = new Jint.Engine(); engine.SetValue("demoJSApi", new DemoJavascriptApi()); // var result = engine.Execute("demoJSApi.helloWorldFromDotNet('TestTest');demoJSApi.helloWorldFromDotNet('TestTest')").GetCompletionValue(); var result = engine.Execute("function test() { return 'test'; };test();").GetCompletionValue(); } So without a .NET callback and it is still 10-20% slower in 64bit mode. Does anyone know why this is? Update #2 This example shows the difference in speed between a 32bit and 64bit process. I have used benchmarkdotnet.org to show the difference. public class Program { static void Main(string[] args) { var summary = BenchmarkRunner.Run<JintTest>(); } } [LegacyJitX86Job, LegacyJitX64Job] [MinColumn, MaxColumn, MeanColumn, MedianColumn] public class JintTest { public JintTest() { Test(); } private const string ContextString = #"/*var test1;var LineupScheduleItem1 = undefined;var LineupScheduleItem2 = undefined;var LineupScheduleItem3 = undefined;var LineupScheduleItem4 = undefined;*/"; [Benchmark] public void Test() { Jint.Engine engine = new Jint.Engine(cfg => { cfg.Strict(true); // Good practive for javascript cfg.AllowClr(); // Access to .net cfg.LimitRecursion(16); // Help stop broken scripts taking down application cfg.CatchClrExceptions(ex => ex is Exception); }); JavaScriptParser parser = new Jint.Parser.JavaScriptParser(true); int maxIterations = 500; for (int i = 0; i < maxIterations; i++) { engine.Execute(parser.Parse(ContextString)); } } } The script is all commented out and it is just being executed. The 64bit process is about 30% slower. It does not seem to make a difference if the script is parsed or not. Does anyone know why this would be the case?
Call JavaScript from C# object in Xamarin
I have been able to call JavaScript from C# inside the MainActivity but I'm trying to do so from an object. The majority of my app runs inside a WebView, my JavaScript calls to my C# Interface invoking an asynchronous function and when it's complete I would like to call back to my JavaScript but am unable to do so. Here is my current setup: In my MainActivity I setup my WebView as such: browser = FindViewById<WebView>(Resource.Id.mainView); browser.SetInitialScale(1); browser.SetWebChromeClient(new GeoWebChromeClient()); browser.Settings.UseWideViewPort = true; browser.Settings.LoadWithOverviewMode = true; if (Build.VERSION.SdkInt >= BuildVersionCodes.Kitkat) { WebView.SetWebContentsDebuggingEnabled(true); } browser.Settings.SetGeolocationEnabled(true); browser.Settings.JavaScriptEnabled = true; browser.AddJavascriptInterface(new JSCSMedium(this, ref browser), "Android"); browser.LoadUrl("file:///android_asset/web/index.html"); Then inside the JSCSMedium object I have an asynch function: [Export] [JavascriptInterface] public void SyncApps() { Task t = Task.Run(() => { IList<ApplicationInfo> tempApps = Application.Context.PackageManager.GetInstalledApplications(PackageInfoFlags.MatchDirectBootAware); string packageName = ""; string appName = ""; for (int i = 0; i < tempApps.Count(); i++) { packageName = tempApps[i].PackageName; appName = tempApps[i].LoadLabel(manager); var root = System.Environment.GetFolderPath(System.Environment.SpecialFolder.Personal); var filePath = System.IO.Path.Combine(root, "system"); filePath = System.IO.Path.Combine(filePath, packageName); if (!System.IO.Directory.Exists(filePath)) { System.IO.Directory.CreateDirectory(filePath); } filePath = System.IO.Path.Combine(filePath, "icon.png"); if (!System.IO.File.Exists(filePath)) { Drawable icon = tempApps[i].LoadIcon(Application.Context.PackageManager); BitmapDrawable bd = (BitmapDrawable)icon; CreateAppIcon(bd.Bitmap, packageName); } Intent intent = Application.Context.PackageManager.GetLaunchIntentForPackage(packageName); if (intent != null) { apps.Add(tempApps[i]); } } }); } If I don't do the C# as an async function it runs and returns data fine, but this process takes a bit of time and blocks the app temporarily. Inside my MainActivity I can call JavaScript just fine with: browser.EvaluateJavascript("javascript: alert('fooBar');", null); But browser is not accessible inside the JSCSMedium. I've tried passing the browser object as a reference and normally but it throws an exception stating that the EvaluateJavascript function must be called on the same thread as where it was instantiated. I've also tried sending a reference of my MainActivity to the JSCSMedium and call a function inside the MainActivity to run the EvaluateJavascript but it seems to do nothing. No error, not crash, just nothing.
The problem is Task.Run forces the code to run in the thread pool, and browser.EvaluateJavascript needs to run on the main thread. You have at least two options here, depending on your needs: 1) Run the EvaluateJavascript call inside the Task.Run block with something like: var h = new Handler(Looper.MainLooper); h.Post(() => browser.EvaluateJavascript("javascript: alert('fooBar');", null)); 2) Run the EvaluateJavascript call outside the Task.Run block: [Export] [JavascriptInterface] public async void SyncApps() { await Task.Run(() => { //... }); browser.EvaluateJavascript("javascript: alert('fooBar');", null); } Not really sure if you can change the return type of SyncApps(). If JS doesn't complain, you better change that too.
Why does electron gives access violation trying to access a new Isolate?
I wrote a Node.js addon to be used in Electron framework The main entry of the addon calls another C++ library that makes a long running operation so I put callbacks in to have reports of the operation progress. So the C++ library calls a callback in my addon but the Isolate is null so I tried to create a new one The isolate is created well but when I try to use it to have new Local i had this error: Exception thrown at 0x0000000000000000 in electron.exe: 0xC0000005: Access violation executing location 0x0000000000000000. Here is an excerpt of my code (the progress_cb variable takes value in the main entry of the addon casting the function passed in Javascript call): Local<Function> progress_cb; class ArrayBufferAllocator : public ArrayBuffer::Allocator { public: virtual void* Allocate(size_t length) { void* data = AllocateUninitialized(length); return data == NULL ? data : memset(data, 0, length); } virtual void* AllocateUninitialized(size_t length) { return malloc(length); } virtual void Free(void* data, size_t) { free(data); } }; void progress_callback(int read, int write, int total) { V8::Initialize(); ArrayBufferAllocator allocator; Isolate::CreateParams params; params.array_buffer_allocator = &allocator; Isolate* isolate = Isolate::GetCurrent(); if (!isolate) { isolate = Isolate::New(params); isolate->Enter(); } const unsigned argc = 3; Local<Integer> r = Int32::New(isolate, (int32_t)read); Local<Integer> w = Int32::New(isolate, (int32_t)write); Local<Value> t = Int32::New(isolate, (int32_t)total); Local<Value> argv[argc] = { r, w, t }; progress_cb->Call(isolate->GetCurrentContext()->Global(), argc, argv); } Any suggestion?
How to launch a normal download from a Firefox Add-on SDK extension
I'm developing an Add-on SDK extension for Firefox. I find that I need to be able to launch a download as if the user requested it, that is, either showing the normal file save dialog or saving the file to wherever the user prefers, as it could be configured under preferences > content. Every single post or documentation regarding downloads seem to only take in consideration the scenario where I know where to download the file, but that is not what I need in this case. In this case, it needs to be as if the user started the download. How can this be accomplished, preferably via the methods of the SDK?
Well, you could just initiate an actual save. Initiating a save link from your code: In the context menu the oncommand value is gContextMenu.saveLink();. saveLink() is defined in: chrome://browser/content/nsContextMenu.js. It does some housekeeping and then calls saveHelper() which is defined in the same file. You could just call saveHelper() with appropriate arguments. It is included in panels from chrome://browser/content/web-panels.xul with: <script type="application/javascript" src="chrome://browser/content/nsContextMenu.js"/> Then the gContextMenu variable declared in chrome://browser/content/browser.js as null is assigned: gContextMenu = new nsContextMenu(this, event.shiftKey); in the onpopupshowing event handler for context menus. It is returned to: 'gContextMenu = null;' in the onpopuphiding event handler. If you want to use it in your own code you can do: let urlToSave = "http://stackoverflow.com/questions/26694442"; let linkText = "Some Link text"; // Add a "/" to un-comment the code appropriate for your add-on type. /* Overlay and bootstrap: const Cc = Components.classes; const Ci = Components.interfaces; const Cr = Components.results; //*/ /* Add-on SDK: var {Cc, Ci, Cr} = require("chrome"); //*/ if (window === null || typeof window !== "object") { //If you do not already have a window reference, you need to obtain one: // Add a "/" to un-comment the code appropriate for your add-on type. /* Add-on SDK: var window = require('sdk/window/utils').getMostRecentBrowserWindow(); //*/ /* Overlay and bootstrap (from almost any context/scope): var window=Components.classes["#mozilla.org/appshell/window-mediator;1"] .getService(Components.interfaces.nsIWindowMediator) .getMostRecentWindow("navigator:browser"); //*/ } //Create an object in which we attach nsContextMenu.js. // It needs some support properties/functions which // nsContextMenu.js assumes are part of its context. let contextMenuObj = { makeURI: function (aURL, aOriginCharset, aBaseURI) { var ioService = Cc["#mozilla.org/network/io-service;1"] .getService(Ci.nsIIOService); return ioService.newURI(aURL, aOriginCharset, aBaseURI); }, gPrefService: Cc["#mozilla.org/preferences-service;1"] .getService(Ci.nsIPrefService) .QueryInterface(Ci.nsIPrefBranch), Cc: Cc, Ci: Ci, Cr: Cr }; Cc["#mozilla.org/moz/jssubscript-loader;1"] .getService(Ci.mozIJSSubScriptLoader) .loadSubScript("chrome://browser/content/nsContextMenu.js" ,contextMenuObj); //Re-define the initMenu function, as there is not a desire to actually // initialize a menu. contextMenuObj.nsContextMenu.prototype.initMenu = function() { }; let myContextMenu = new contextMenuObj.nsContextMenu(); //Save the specified URL myContextMenu.saveHelper(urlToSave,linkText,null,true,window.content.document); Alternative to using loadSubScript to load nsContextMenu.js: My preference is to use loadSubScript to load the saveHelper code from nsContextMenu.js. This keeps the code up to date with any changes which are made in future Firefox releases. However, it introduces the dependency that you are using a function from a non-official API. Thus, it might change in some way in future Firefox release and require changes in your add-on. The alternative is to duplicate the saveHelper() code in your extension. It is defined as the following: // Helper function to wait for appropriate MIME-type headers and // then prompt the user with a file picker saveHelper: function(linkURL, linkText, dialogTitle, bypassCache, doc) { // canonical def in nsURILoader.h const NS_ERROR_SAVE_LINK_AS_TIMEOUT = 0x805d0020; // an object to proxy the data through to // nsIExternalHelperAppService.doContent, which will wait for the // appropriate MIME-type headers and then prompt the user with a // file picker function saveAsListener() {} saveAsListener.prototype = { extListener: null, onStartRequest: function saveLinkAs_onStartRequest(aRequest, aContext) { // if the timer fired, the error status will have been caused by that, // and we'll be restarting in onStopRequest, so no reason to notify // the user if (aRequest.status == NS_ERROR_SAVE_LINK_AS_TIMEOUT) return; timer.cancel(); // some other error occured; notify the user... if (!Components.isSuccessCode(aRequest.status)) { try { const sbs = Cc["#mozilla.org/intl/stringbundle;1"]. getService(Ci.nsIStringBundleService); const bundle = sbs.createBundle( "chrome://mozapps/locale/downloads/downloads.properties"); const title = bundle.GetStringFromName("downloadErrorAlertTitle"); const msg = bundle.GetStringFromName("downloadErrorGeneric"); const promptSvc = Cc["#mozilla.org/embedcomp/prompt-service;1"]. getService(Ci.nsIPromptService); promptSvc.alert(doc.defaultView, title, msg); } catch (ex) {} return; } var extHelperAppSvc = Cc["#mozilla.org/uriloader/external-helper-app-service;1"]. getService(Ci.nsIExternalHelperAppService); var channel = aRequest.QueryInterface(Ci.nsIChannel); this.extListener = extHelperAppSvc.doContent(channel.contentType, aRequest, doc.defaultView, true); this.extListener.onStartRequest(aRequest, aContext); }, onStopRequest: function saveLinkAs_onStopRequest(aRequest, aContext, aStatusCode) { if (aStatusCode == NS_ERROR_SAVE_LINK_AS_TIMEOUT) { // do it the old fashioned way, which will pick the best filename // it can without waiting. saveURL(linkURL, linkText, dialogTitle, bypassCache, false, doc.documentURIObject, doc); } if (this.extListener) this.extListener.onStopRequest(aRequest, aContext, aStatusCode); }, onDataAvailable: function saveLinkAs_onDataAvailable(aRequest, aContext, aInputStream, aOffset, aCount) { this.extListener.onDataAvailable(aRequest, aContext, aInputStream, aOffset, aCount); } } function callbacks() {} callbacks.prototype = { getInterface: function sLA_callbacks_getInterface(aIID) { if (aIID.equals(Ci.nsIAuthPrompt) || aIID.equals(Ci.nsIAuthPrompt2)) { // If the channel demands authentication prompt, we must cancel it // because the save-as-timer would expire and cancel the channel // before we get credentials from user. Both authentication dialog // and save as dialog would appear on the screen as we fall back to // the old fashioned way after the timeout. timer.cancel(); channel.cancel(NS_ERROR_SAVE_LINK_AS_TIMEOUT); } throw Cr.NS_ERROR_NO_INTERFACE; } } // if it we don't have the headers after a short time, the user // won't have received any feedback from their click. that's bad. so // we give up waiting for the filename. function timerCallback() {} timerCallback.prototype = { notify: function sLA_timer_notify(aTimer) { channel.cancel(NS_ERROR_SAVE_LINK_AS_TIMEOUT); return; } } // set up a channel to do the saving var ioService = Cc["#mozilla.org/network/io-service;1"]. getService(Ci.nsIIOService); var channel = ioService.newChannelFromURI(makeURI(linkURL)); if (channel instanceof Ci.nsIPrivateBrowsingChannel) { let docIsPrivate = PrivateBrowsingUtils.isWindowPrivate(doc.defaultView); channel.setPrivate(docIsPrivate); } channel.notificationCallbacks = new callbacks(); let flags = Ci.nsIChannel.LOAD_CALL_CONTENT_SNIFFERS; if (bypassCache) flags |= Ci.nsIRequest.LOAD_BYPASS_CACHE; if (channel instanceof Ci.nsICachingChannel) flags |= Ci.nsICachingChannel.LOAD_BYPASS_LOCAL_CACHE_IF_BUSY; channel.loadFlags |= flags; if (channel instanceof Ci.nsIHttpChannel) { channel.referrer = doc.documentURIObject; if (channel instanceof Ci.nsIHttpChannelInternal) channel.forceAllowThirdPartyCookie = true; } // fallback to the old way if we don't see the headers quickly var timeToWait = gPrefService.getIntPref("browser.download.saveLinkAsFilenameTimeout"); var timer = Cc["#mozilla.org/timer;1"].createInstance(Ci.nsITimer); timer.initWithCallback(new timerCallback(), timeToWait, timer.TYPE_ONE_SHOT); // kick off the channel with our proxy object as the listener channel.asyncOpen(new saveAsListener(), null); }
Like #canuckistani said, use the Downloads.jsm let { Downloads } = require("resource://gre/modules/Downloads.jsm"); let { OS } = require("resource://gre/modules/osfile.jsm") let { Task } = require("resource://gre/modules/Task.jsm"); Task.spawn(function () { yield Downloads.fetch("http://www.mozilla.org/", OS.Path.join(OS.Constants.Path.tmpDir, "example-download.html")); console.log("example-download.html has been downloaded."); }).then(null, Components.utils.reportError);
Android Synchronous Javascript with WebView on Lollipop
Using a technique borrowed from http://www.gutterbling.com/blog/synchronous-javascript-evaluation-in-android-webview/ we have successfully implemented a number of features within our app that allow our Android app to synchronously get data from a Webview. Here's the example from gutterbling: import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; import android.content.Context; import android.util.Log; import android.webkit.WebView; /** * Provides an interface for getting synchronous javascript calls * #author btate * */ public class SynchronousJavascriptInterface { /** The TAG for logging. */ private static final String TAG = "SynchronousJavascriptInterface"; /** The javascript interface name for adding to web view. */ private final String interfaceName = "SynchJS"; /** Countdown latch used to wait for result. */ private CountDownLatch latch = null; /** Return value to wait for. */ private String returnValue; /** * Base Constructor. */ public SynchronousJavascriptInterface() { } /** * Evaluates the expression and returns the value. * #param webView * #param expression * #return */ public String getJSValue(WebView webView, String expression) { latch = new CountDownLatch(1); String code = "javascript:window." + interfaceName + ".setValue((function(){try{return " + expression + "+\"\";}catch(js_eval_err){return '';}})());"; webView.loadUrl(code); try { // Set a 1 second timeout in case there's an error latch.await(1, TimeUnit.SECONDS); return returnValue; } catch (InterruptedException e) { Log.e(TAG, "Interrupted", e); } return null; } /** * Receives the value from the javascript. * #param value */ public void setValue(String value) { returnValue = value; try { latch.countDown(); } catch (Exception e) {} } /** * Gets the interface name * #return */ public String getInterfaceName(){ return this.interfaceName; } } This JS Interface is used like this: WebView webView = new WebView(context); SynchronousJavascriptInterface jsInterface = new jsInterface(); webView.addJavascriptInterface(jsInterface, jsInterface.getInterfaceName()); String jsResult = jsInterface.getJSValue(webView, "2 + 5"); Despite working nicely in Android 4.0 - 4.4.4 this is not working for us with Android 5.0 (Lollipop). It appears as though in Lollipop the JS executes after the latch countdown has completed, whereas previously it would return a value prior to the countdown completing. Has something changed with the threads that JS in an Webview executes on? And is there any way that I can fix this without re-writing the large chunks of our app that depend on being able to call the JS synchronously?
loadUrl("javascript:" + code) don't work with API > 19, instead use: evaluateJavascript(code, null); Or, you can improve your code to use the callback provided by evaluateJavascript, though.