I'm writing an HTML5 / JavaScript app for multiple devices, but my issue comes when writing it for Win8 and iOS.
Due to Win8's strict security, I need to wrap certain functions, specifically those that inject HTML or divs into the window, in a WinJS function called execUnsafeLocalFunction.
When I go to run this same app on iOS, it cannot run those functions because WinJS does not exist.
How can I create a conditional statement so that it checks if the device running the app is a Win8 device, or iOS? This way I can tell it to run function foo or function bar.
JavaScript doesn't presently support conditional compilation, so I think you need to write your own stub functions that wrap those like execUnsafeLocalFunction. In that function you'd test whether the function exists by checking the namespaces. Something like:
function callUnsafeFunction(func) {
if (MSApp && MSApp.execUnsafeLocalFunction) {
return MSApp.execUnsafeLocalFunction(func);
} else {
return func()
}
}
Alternately, you can check if the function exists in the namespace, and if not, add you own function of the same name as a passthrough, something like this (I haven't tested such code):
if (!(MSApp && MSApp.execUnsafeLocalFUnction)) {
window.MSApp = {
execUnsafeLocalFunction: function(func) {
func();
}
};
}
In short, the conditional check is the existence of a namespace/function, because we don't have conditional compilation.
Related
I need to check for fullscreen support with my Go WASM Canvas project, before switching to fullscreen mode. I have the following code so far:
var fullscreenFunc js.Value
var fullscreenNotSupported bool
set with the following logic:
fullscreenFunc = app.Get("requestFullscreen")
if fullscreenFunc.IsUndefined() {
fullscreenFunc = app.Get("mozRequestFullScreen")
if fullscreenFunc.IsUndefined() {
fullscreenFunc = app.Get("webkitRequestFullscreen")
if fullscreenFunc.IsUndefined() {
fullscreenFunc = app.Get("msRequestFullscreen")
if fullscreenFunc.IsUndefined() {
fullscreenNotSupported = true
println("Fullscreen not supported")
}
}
}
}
I was expecting to be able to call the correct function with js.Invoke, but I see no way to tell the Invoke upon which object the call should be made. My 'app' value is being interpreted just as a param.
func Fullscreen(app js.Value) {
if fullscreenNotSupported {
return
}
fullscreenFunc.Invoke(app)
}
resulting in:
panic: JavaScript error: 'mozRequestFullScreen' called on an object that does not implement interface Element.
So am I correct in my thinking that the only way I can call the correct method, is not to store the Function, but to store a string of the function name, and then 'invoke' / 'call' it using the following approach?
app.Call(fullscreenFunctionNameString)
It feels like I misunderstood the purpose of Invoke. Is it only for js.Global() type calls?
[edit] Using 'Call', at least it seems possible to derive the function name without having to repeat the above browser specifics:
fullscreenFunctionName = fullscreenFunc.Get("name").String()
app.Call(fullscreenFunctionNameString)
It doesn't answer the question, but is probably of help to someone trying to do the same.
The arguments to invoke get turned into arguments for the javascript function it wraps. Since those fullscreen functions don't need any arguments, I think you might just need to change:
fullscreenFunc.Invoke(app)
To:
fullscreenFunc.Invoke()
...assuming app is the same JS element in both places. If not your Call solution is probably your best bet.
Development Environment OS: Windows 7 Enterprise LTS
Browser compatibility minimum requirements: Should support all Edge, Firefox, Chrome browsers, as of 2018.
Current ongoing issue: Unable to run VM on dev workstation; Cannot run Windows 10 VMs to debug Microsoft Edge extensions.
To explain:
An "all-in-one browser extension" refers to a browser extension code that uses the same code with minor differences to work on various WebExtensions / Chrome Extensions supported browsers. At bare minimum, the same codebase should work and run on Edge, Firefox, and Chrome with very minor changes.
Callbacks on the content scripts for Edge/Firefox/Chrome extensions are handled differently.
For unknown reasons, I cannot run VM on my workstation machine. When VM is running, VM client is black. This is a localized issue on my end that I cannot resolve, so I'm forced to find a different solution/alternative.
How are they handled differently on the content scripts:
Edge: browser.runtime.sendMessage uses callbacks, and returns undefined.
Firefox: browser.runtime.sendMessage uses Promises, and returns a Promise.
Chrome: chrome.runtime.sendMessage uses callbacks, and returns undefined.
According to various references:
Firefox / Chrome / MS Edge extensions using chrome.* or browser.*
https://www.smashingmagazine.com/2017/04/browser-extension-edge-chrome-firefox-opera-brave-vivaldi/
On the content scripts, you can declare the following JavaScript snippet at the top in order to create a global variable that can be referenced everywhere else:
//Global "browser" namespace definition.
window.browser = (function() {
return window.msBrowser || window.browser || window.chrome;
})();
Unfortunately, because of the issue I'm experiencing (VM not running), I cannot tell if window.msBrowser is still being used. And this solution is not helpful for me when handling message callbacks when using namespace.runtime.sendMessage.
With all that said, my main question is: How to write a message passing function that can handle callbacks properly?
Currently, I'm using the following code:
function sendGlobalMessage(messageRequest, callback) {
if (chrome && window.openDatabase) {
//This is Chrome browser
chrome.runtime.sendMessage(messageRequest, callback);
}
else if (browser) {
try {
//Edge will error out because of a quirk in Edge IndexedDB implementation.
//See https://gist.github.com/nolanlawson/a841ee23436410f37168
let db = window.indexedDB.open("edge", (Math.pow(2, 30) + 1));
db.onerror = function(e) {
throw new Error("edge is found");
};
db.onsuccess = function(e) {
//This is Firefox browser.
browser.runtime.sendMessage(messageRequest).then(callback);
};
}
catch (e) {
//This is Edge browser
browser.runtime.sendMessage(messageRequest, callback);
}
}
}
I truly felt this is a hacky solution, because the code is based off of browser platform exclusive quirks in order to separate chrome.runtime.sendMessage and browser.runtime.sendMessage API calls, so as to handle callbacks in their respective platforms. I really wanted to change this.
So I'm asking what better ways are there, out there, that is useful to detect the different platforms, and handle message passing callbacks properly at the same time?
Thanks in advance.
I believed I solved it.
EDIT: The FINAL final version (updated and more stable, less message passing):
//Global "browser" namespace definition, defined as "namespace". Can be renamed to anything else.
window.namespace = (function() {
return window.browser || window.chrome;
})();
function sendGlobalResponse(message, callback){
if (window.namespace === window.chrome) {
//Chrome
window.namespace.runtime.sendMessage(message, callback);
}
else if (window.namespace === window.browser) {
//Using instanceof to check for object type, and use the returned evaluation as a truthy value.
let supportPromises = false;
try {
supportPromises = window.namespace.runtime.getPlatformInfo() instanceof Promise;
}
catch(e) { }
if (supportPromises){
//Firefox
window.namespace.runtime.sendMessage(message).then(callback);
}
else {
//Edge
window.namespace.runtime.sendMessage(message, callback);
}
}
}
(Original Post):
The final version (Now obsoleted):
//Global "browser" namespace definition.
window.namespace = (function() {
return window.browser || window.chrome;
})();
function sendGlobalResponse(message, callback){
if (window.namespace === window.chrome) {
//Chrome
window.namespace.runtime.sendMessage(message, callback);
}
else if (window.namespace === window.browser) {
let returnValue = window.namespace.runtime.sendMessage({});
if (typeof returnValue === "undefined"){
//Edge
window.namespace.runtime.sendMessage(message, callback);
}
else {
//Firefox
window.namespace.runtime.sendMessage(message).then(callback);
}
}
}
In the second if statement, by checking to see if the return value of a window.browser.runtime.sendMessage is a Promise or undefined, we can detect if the platform is Firefox or Edge.
I think this is the only solution to handle message passing callbacks/message responses on the content scripts.
I really couldn't think of a better solution than this. So I'll be using this from now on.
But if anyone else knows a better way, a way where you don't need to send out 1 extra dummy message for Firefox and Edge per function call, that would be great!
It sucks that anything inside the content script is not persistent, and even if you store information about what platform the code is being run on, you still have to fetch the information from the background script before filtering out which runtime.sendMessage function to call on, so it doesn't really save much time.
I am writing an iOS App (using xcode 7.3 and swift 2.2) using JavascriptCode framework. Calling javascript methods from swift works perfect, but when I call the swift method from javascript, xcode simply shows a "loading" type of symbol and nothing happens. I need to "force quit" xcode to get out of this state.
I have followed https://www.raywenderlich.com/124075/javascriptcore-tutorial and http://nshipster.com/javascriptcore/ and I am trying pretty simple calls.
Has anyone faced this kind of issue?
My swift code is as follows:
#objc protocol WindowJSExports : JSExport {
var name: String { get set }
func getName() -> String
static func createWindowWithName(name: String) -> WindowJS
}
#objc class WindowJS : NSObject, WindowJSExports {
dynamic var name: String
init(name: String) {
self.name = name
}
class func createWindowWithName(name: String) -> WindowJS {
return WindowJS(name: name)
}
func getName() -> String {
NSLog("getName called from JS context")
return "\(name)"
}
}
I am initializing the context as follows:
runContext = JSContext()
runContext.name = "test_Context"
windowToJs = WindowJS(name: "test")
runContext.setObject(windowToJs.self, forKeyedSubscript: "WindowJS")
If I replace the last two lines in above code with below code without instantiating it, the code simply fails to load.
runContext.setObject(WindowJS.self, forKeyedSubscript: "WindowJS")
And the javascript code is as simple as
function check() {
return WindowJS.getName()
}
I do see the breakpoint being hit in the JS function check and when the WindowJS.getName gets called, xcode simply becomes unresponsive.
The setTimeout could be solved by adding following piece of code to my swift function.
let setTimeout: #convention(block) (JSValue, Int) -> () =
{ callback, timeout in
let timeVal = Int64(timeout)
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, timeVal), dispatch_get_main_queue(), { callback.callWithArguments(nil)})
}
To expose this native code to the JS context, I also added following.
runContext.setObject(unsafeBitCast(setTimeout, AnyObject.self), forKeyedSubscript: "setTimeout")
Things then worked fine.
You're creating a deadlock since you are calling from Swift to JavaScript back to Swift. I'm not sure exactly why it is a deadlock but I had a similar issue with WKWebView on Mac recently.
You need to decouple this and make the communication asynchronous. This obviously means you cannot simply return a value from your JS function in this case.
To decouple, you can break the deadlock by deferring the work the JavaScript function needs to do out of the current runloop iteration using setTimeout:
function myFunction() {
setTimeout(function() {
// The actual work is done here.
// Call the Swift part here.
}, 0);
}
The whole native ↔︎ JavaScript communication is very, very tricky. Avoid it if you can. There's a project called XWebView that may be able to help you as it tries to ease bridging between the two worlds.
With Firefox 17.0.1 I am using an add-on called KeyConfig 20110522 to set some new hot keys and also set the acceltext of menuitems for my new keys as well as for add-ons that do not bother to do so.
I want the acceltext of the menuitems to be set when Firefox starts, but currently I am just using a hot key to execute the following code against the UI via KeyConfig:
document.getElementById("tabmix-menu")
.setAttribute("acceltext","Alt+Ctrl+Shift+T");
// more of the same...
I need a couple of beginners tips:
How can I execute arbitrary code against the UI in the same way as I execute against an HTML page via the console?
Is there a sneaky way to get a clump of code to execute on browser start-up without delving into XUL development?
Is there a way to trace commands executed against the UI so I can get at command calls instead of using triggers when I set my hot keys like so:
document.getElementById("tabmix-menu").click();
Any other tips on this type of low-level hacking would also be welcome.
You can execute arbitrary code against the Firefox UI from an addon, but as you say, doing all the XUL related stuff is a bit boring :-)
Enter "Bootstrapped" extensions!
Part 1:
A "Bootstrapped" (or re-startless) extension needs only an install.rdf file to identify the addon, and a bootstrap.js file to implement the bootstrap interface.
Bootstrapped Extension: https://developer.mozilla.org/en-US/docs/Extensions/Bootstrapped_extensions
Good example: http://blog.fpmurphy.com/2011/02/firefox-4-restartless-add-ons.html
The bootstrap interface can be implemented very simply:
function install() {}
function uninstall() {}
function shutdown(data, reason) {}
function startup(data, reason) { /* YOUR ARBITRARY CODE HERE! */ }
You compile the extension by putting install.rdf and bootstrap.js into the top-level of a new zip file, and rename the zip file extension to .xpi.
Part 2:
Your code is privileged and can use any of the Mozilla platform APIs. There is however an issue of timing. The moment-in-time at which the "startup" function is executed is one at which no Chrome window objects exist yet!
If it's important for your code that you have a Chrome Window, we need to wait for it to appear:
// useful services.
Cu.import("resource://gre/modules/Services.jsm");
var loader = Cc["#mozilla.org/moz/jssubscript-loader;1"]
.getService(Ci.mozIJSSubScriptLoader);
var wmSvc = Cc["#mozilla.org/appshell/window-mediator;1"]
.getService(Ci.nsIWindowMediator);
var logSvc = Cc["#mozilla.org/consoleservice;1"]
.getService(Ci.nsIConsoleService);
// get the first gBrowser
var done_startup = 0;
var windowListener;
function do_startup(win) {
if (done_startup) return;
done_startup = 1;
wmSvc.removeListener(windowListener);
var browserEnum = wmSvc.getEnumerator("navigator:browser");
var browserWin = browserEnum.getNext();
var tabbrowser = browserWin.gBrowser;
/* your code goes here! */
}
// window listener implementation
windowListener = {
onWindowTitleChange: function(aWindow, aTitle) {},
onCloseWindow: function(aWindow) {},
onOpenWindow: function(aWindow) {
var win = aWindow.QueryInterface(Ci.nsIInterfaceRequestor)
.getInterface(Ci.nsIDOMWindowInternal || Ci.nsIDOMWindow);
win.addEventListener("load", function(aEvent) {
win.removeEventListener("load", arguments.callee, false);
if (aEvent.originalTarget.nodeName != "#document") return;
do_startup();
}
};
// CODE ENTRY POINT (put this in bootstrap "startup" function)
wmSvc.addListener(windowListener);
I want a translator in my Firefox. I find some code from internet. but it doesn't run in my Firefox. I have installed Greasemonkey.
function loadBingTranslator() {
script = document.createElement('script');
script.src = 'http://dict.bing.com.cn/cloudwidget/Scripts/Generated/BingTranslate_Selection_ShowIcon.js';
script.onload = initBingTranslator;
document.body.appendChild(script);
};
function initBingTranslator() {
BingCW.Init({
MachineTranslation: true,
WebDefinition: true
});
}
loadBingTranslator();
Such a script must account for the GM sandbox, and also (usually) allow time for the library to load and initialize. See Avoid Common Pitfalls (in Greasemonkey).
So, you would use this library like so:
//--- Load the library.
var D = document;
var appTarg = D.getElementsByTagName ('head')[0] || D.body || D.documentElement;
var jsNode = D.createElement ('script');
jsNode.src = 'http://dict.bing.com.cn/cloudwidget/Scripts/Generated/BingTranslate_Selection_ShowIcon.js';
jsNode.addEventListener ("load", initBingTranslatorOnDelay, false);
appTarg.appendChild (jsNode);
//--- Allow some time for the library to initialize after loading.
function initBingTranslatorOnDelay () {
setTimeout (initBingTranslator, 666);
}
//--- Call the library's start-up function, if any. Note needed use of unsafeWindow.
function initBingTranslator () {
unsafeWindow.BingCW.Init ( {
AppID: "GM Foo",
MachineTranslation: true,
WebDefinition: true
} );
}
Issues, some specific to this question:
onload is not available; See the pitfalls. Event handlers cannot be set this way in GM. Also, addEventListener() is the best practice anyway.
Accessing JS (including libraries we load) in the page scope, requires unsafeWindow.
That app appears to want an AppID.
Sometimes, libraries like this can be loaded in the GM scope instead of the page scope, using the // #require directive.
I did not try that with this library, but with others, it may be possible. Do not try this with untrusted libraries, as they gain extra abilities to infect your machine, once inside the GM scope.
Don't use reserved words, like "script", for variable names.
My JavaScript Console is outputting a "Component is not available"
line 10: script.onload = initBingTranslator;
So I fixed changed it to ... = initBingTranslator() because it is a function.
Now it says "BingCW is not definded"
Line 15: BingCW.Init({
MachineTranslation: true,
WebDefinition: true
});
And it is right, not sure if something is missing or this is supposed to only work in IE, I would find a Google translator solution personally (or just use an existing add-on).
Bing Dictionary hasd published a Firefox addon.
You can use it directly.