How can I access the DOM of a <webview> in Electron? - javascript

I'm just getting started with Electron, with prior experience with node-webkit (nw.js).
In nw.js, I was able to create iframes and then access the DOM of said iframe in order to grab things like the title, favicon, &c. When I picked up Electron a few days ago to port my nw.js app to it, I saw advice to use webviews instead of iframes, simply because they were better. Now, the functionality I mentioned above was relatively easy to do in nw.js, but I don't know how to do it in Electron (and examples are slim to none). Can anyone help?
Also, I have back/forward buttons for my webview (and I intend on having more than one). I saw in the documentation that I could call functions for doing so on a webview, but nothing I have tried worked either (and, I haven't found examples of them being used in the wild).

I dunno who voted to close my question, but I'm glad it didn't go through. Other people have this question elsewhere online too. I also explained what I wanted to achieve, but w/e.
I ended up using ipc-message. The documentation could use more examples/explanations for the layperson, but hey, I figured it out. My code is here and here, but I will also post examples below should my code disappear for whatever reason.
This code is in aries.js, and this file is included in the main renderer page, which is index.html.
var ipc = require("ipc");
var webview = document.getElementsByClassName("tabs-pane active")[0];
webview.addEventListener("ipc-message", function (e) {
if (e.channel === "window-data") {
// console.log(e.args[0]);
$(".tab.active .tab-favicon").attr("src", e.args[0].favicon);
$(".tab.active .tab-title").html(e.args[0].title);
$("#url-bar").val(e.args[0].url);
$("#aries-titlebar h1").html("Aries | " + e.args[0].title);
}
// TODO
// Make this better...cancel out setTimeout?
var timer;
if (e.channel === "mouseover-href") {
// console.log(e.args[0]);
$(".linker").html(e.args[0]).stop().addClass("active");
clearTimeout(timer);
timer = setTimeout(function () {
$(".linker").stop().removeClass("active");
}, 1500);
}
});
This next bit of code is in browser.js, and this file gets injected into my <webview>.
var ipc = require("ipc");
document.addEventListener("mouseover", function (e) {
var hoveredEl = e.target;
if (hoveredEl.tagName !== "A") {
return;
}
ipc.sendToHost("mouseover-href", hoveredEl.href);
});
document.addEventListener("DOMContentLoaded", function () {
var data = {
"title": document.title,
"url": window.location.href,
// need to make my own version, can't rely on Google forever
// maybe have this URL fetcher hosted on hikar.io?
"favicon": "https://www.google.com/s2/favicons?domain=" + window.location.href
};
ipc.sendToHost("window-data", data);
});
I haven't found a reliable way to inject jQuery into <webview>s, and I probably shouldn't because the page I would be injecting might already have it (in case you're wondering why my main code is jQuery, but there's also regular JavaScript).

Besides guest to host IPC calls as NetOperatorWibby, it is also very useful to go from host to guest. The only way to do this at present is to use the <webview>.executeJavaScript(code, userGesture). This api is a bit crude but it works.
If you are working with a remote guest, like "extending" a third party web page, you can also utilize webview preload attribute which executes your custom script before any other scripts are run on the page. Just note that the preload api, for security reasons, will nuke any functions that are created in the root namespace of your custom JS file when your custom script finishes, however this custodial process will not nuke any objects you declare in the root. So if you want your custom functions to persist, bundle them into a singleton object and your custom APIs will persist after the page fully loads.
[update] Here is a simple example that I just finished writing: Electron-Webview-Host-to-Guest-RPC-Sample

This relates to previous answer (I am not allowed to comment): Important info regarding ipc module for users of Electron 1.x:
The ipc module was split into two separate modules:
ipcMain for the main process
ipcRenderer for the renderer process
So, the above examples need to be corrected, instead of
// Outdated - doesn't work in 1.x
var ipc = require("ipc");
use:
// In main process.
var ipcMain = require('electron').ipcMain
And:
// In renderer process.
var ipcRenderer = require('electron').ipcRenderer
See: http://electron.atom.io/blog/2015/11/17/electron-api-changes section on 'Splitting the ipc module'

Related

In newer versions of Firefox, is it still possible to override a web page's JS function?

I am writing an extension to override a web page's JS function, and started from this question, but the answer does not appear to work in Firefox 42 on Linux.
Next, I tried to use exportFunction as described in the documentation, but that also silently failed.
Inside package.json, I have added the following sesction.
"permissions": {
"unsafe-content-script": true
}
Here is my index.js file.
var self = require('sdk/self');
require("sdk/tabs").on("ready", fixGoogle);
function fixGoogle(tab) {
if (tab.url.indexOf("google.com") > -1) {
tab.attach({
contentScriptFile: self.data.url("google-script.js")
});
}
}
Here is my current data/google-script.js.
unsafeWindow.rwt=function(){};
Note that manually typing in rwt=function(){}; to the browser's console achieves the desired effect, as does using a bookmarklet (which requires clicking) but I am writing the plugin to get this automatically every time I use Google.
Is it possible to override the rwt page function using a Firefox extension? If so, what is the correct API to use?
read the documentation you've linked to, specifically the chapter titled Expose functions to page scripts - which links to exportFunction
function blah() {}
exportFunction(blah, unsafeWindow, {defineAs: 'rwt'});
It turns out that the issue is that the redefinition of the function rwt is racing against the original definition and winning. The original runs after and overrides the function I defined, thereby making it look like my redefinition had silently failed.
Once I realized that this was the problem, the easiest hack around it was to add a timeout to the redefinition inside data/google-script.js.
setTimeout(function() {
unsafeWindow.rwt=function(){};
}, 1000);
Thus, the orignal answer is still correct but simply failed to address the race condition.
Even though content scripts share the DOM, they are otherwise isolated from page scripts. As you correctly surmised, one can use unsafeWindow in Firefox to bypass this isolation.
Personally, I don't like the name of unsafeWindow for some reason ;)
Therefore I propose another way to do this: make use of the thing that's shared between these scopes, i. e. DOM.
You can create a page script from a content script:
var script = 'rwt=function()();';
document.addEventListener('DOMContentLoaded', function() {
var scriptEl = document.createElement('script');
scriptEl.textContent = script;
document.head.appendChild(scriptEl);
});
The benefit of this approach is that you can use it in environments without unsafeWindow, e. g. chrome extensions.

Durandal requiring viewmodel from shell

In my app, I have a global player. In the shell module, I require the viewmodel of the player because I want to know if the player is playing, and if so, I add a class to container of the app (which is in the shell).
The problem is that I also need to require the shell from the player VM, because there are some functions that I use across the app that are in the shell.
But when requiring the player module from the shell, requiring the shell from the player returns undefined. If I don't require the player, the shell is passed normally.
shell.js
define(['viewmodels/player'], function(player) {
return {
player: player
}
})
player.js
define(['viewmodels/shell'], function(shell) {
console.log('shell:', shell) // undefined
})
I don't have any ideia of what's going on.
Hm!
I think I we had this problem once. They way it happens is require checks what's need by shell. Then it sees player module and goes to fix that, and inside that you requuire shell. Circular reference. We did solve it though.
We did like this, I'm writing psuedo code but you should be able to try this.
There are lots of ways, this is one easy way to do it. Open your player and do like this.
var shell = require('shell'); //using this style to work around circular reference
I will try to help with some implementation alternatives!
1º! player could be a service (singleton), and be required in bouth viewmodes,
// app/services/player.js
require([], function(){/*Player*/});
// shell
require(['services/player'], function(player){/* SHEll */});
// ohter view
require(['services/player'], function(player){/* other view*/});
2º You can use pub/sub pattern! Durandal has a support for that!
// Alert that play has been clicked
app.trigger('player:play');
// subscribe play
app.on('player:play', doSomething);
// deactivate subscription
app.off('player:play', doSomething);
Check documentation!
Booth work just fine and with low coupling...

Moving created files with JXA

I'm new to JXA scripting, but I'm attempting to troubleshoot some older scripts currently in place here at work. They loop through an InDesign document and create several PDFs based on it. Previously, they would be stored in a folder called "~/PDFExports". However, this doesn't work with 10.10.
If I change the code to just place the PDFs in "~/", it works fine. From there, I'd like to move the files to "~/PDFExports", but I can't seem to find an answer on how to do that. I've seen things about making calls to ObjC, or to call Application('Finder'), but neither work - they both return undefined.
Am I just missing something basic here, or is it really this hard to simply move a file with JXA?
EDIT: Some syntax for how I'm creating the folder in question and how I'm attempting to work with Finder.
//This is called in the Main function of the script, on first run.
var exportFolder = new Folder(exportPath);
if(!exportFolder.exists) {
exportFolder.create();
}
//This is called right after the PDF is created. file is a reference to the
actual PDF file, and destination is a file path string.
function MoveFile(file,destination){
var Finder = Application("Finder");
Application('Finder').move(sourceFile, { to: destinationFolder });
alert("File moved");
}
Adobe apps have long included their own embedded JS interpreter, JS API, and .jsx filename extension. It has nothing to do with JXA, and is not compatible with it.
InDesign's JSX documentation:
http://www.adobe.com/devnet/indesign/documentation.html#idscripting
(BTW, I'd also strongly advise against using JXA for Adobe app automation as it has a lot of missing/broken features and application compatibility problems, and really isn't fit for production work.)
Here's the link to Adobe's InDesign Scripting forum, which is the best place to get help with JSX:
https://forums.adobe.com/community/indesign/indesign_scripting
You could use Cocoa to create the folder
var exportFolder = $.NSHomeDirectory().stringByAppendingPathComponent("PDFExports")
var fileManager = $.NSFileManager.defaultManager
var folderExists = fileManager.fileExistsAtPath(exportFolder)
if (!folderExists) {
fileManager.createDirectoryAtPathWithIntermediateDirectoriesAttributesError(exportFolder, false, $(), $())
}
and to move a file
var success = fileManager.moveItemAtPathToPathError(sourceFile, destinationLocation, $());
if (success) alert("File moved");
Consider that destinationLocation must be the full path including the file name
and both sourceFile and destinationLocation must be NSString objects like exportFolder
Could it be that the folder is missing ? Could be your reference to the folder object not valid ? Any syntax to show ?
I will share some of what I learned about JXA move and duplicate methods. I am not a professional programmer just an attorney that is passionate about automation. My comments come from much trial and error, reading whatever I could find online, and A LOT of struggle. The move method does not work well with Finder. Use the System Events move method instead. The duplicate method in Finder works just fine. The duplicate method does not work well in system events. This is a modified snippet from a script I wrote showing move() using System Events.
(() => {
const strPathTargetFile = '/Users/bretfarve/Documents/MyFolderA/myFile.txt';
const strPathFolder = '/Users/bretfarve/Documents/MyFolderB/';
/* System Events Objects */
const SysEvents = Application('System Events');
const objPathFolder = SysEvents.aliases[strPathFolder];
SysEvents.move(SysEvents.aliases.byName(strPathTargetFile), {to: objPathFolder});
})();

Preloading of Javascript Files

Working on a small engine to run my HTML5 test games, using it as a great way to get deeper into Javascript and have fun at the same time. Succeeding, btw.
However, I just found this cool little script called PreloadJS ( http://www.createjs.com/#!/PreloadJS ). Also using John Resig's classical inheritence class JS. Very cool, enjoying it. I am attempting to use PreloadJS to pull in all of my engine files...but I seem to be having an issue. Here's the code I'm using (keeping it simple on purpose):
var ScriptLoader = Class.extend({ // Want to add functionality to this to allow PHP or inline loading...perhaps later
init: function() {
this.fileList = [
'Empty.js',
'./engine/Scene.js'
];
this.preload;
},
loadProjectSource: function(directory) {
if (this.preload != null) { this.preload.close(); }
this.preload = new createjs.LoadQueue();
this.preload.addEventListener("fileload", this.fileLoaded);
this.preload.addEventListener("error", this.fileError);
this.preload.setMaxConnections(5);
this.loadAnother();
},
loadAnother: function() {
var item = this.fileList.shift();
if(this.fileList.length != 0) {
this.preload.loadFile(item);
}
},
fileLoaded: function(e) {
debug('Loaded ' + e.item.src);
this.loadAnother();
},
fileError: function(e) {
debug('Error ' + e.item.src);
}
}
From my engine instantiation, I'm calling ScriptLoader.loadProjectSource. It's doing nothing but throwing errors, and the documentation on error handling (and loading JS files in general...) is very sparse on the PreloadJS site. It focuses on preloading images (which, admittedly, looks great). Anyway, so yea, it's throwing errors. And it can't be the files, as I tried loading a completely blank JS file (as you can see). Still throwing an error on the Empty.js file.
Annoyed :) Thanks in advance.
The PreloadJS script uses XHR where available with browser support. In order for this to function correctly with locally-hosted scripts, a local webserver must be running. Upon activating my local webserver and attempting the same operation, full success.

Javascript Build Tools To Toggle Urls/Variables Between Production/Deployment

I am beginning my first big javascript project! I had a question about deployment. I am using ajax calls to a webservice. To set this up I have a static JS file with code like:
var API_URL_ROOT = 'http://api.example.com/';
var IN_DEVELOPMENT = True;
if (IN_DEVELOPMENT) {
API_URL_ROOT = 'http://localhost.com/api';
}
$.get(API_URL_ROOT)
I am using python/fabric to deploy. I was wondering if there were any prebuilt tools for handling the static analysis/manipulation of the javascript files., Right now it leaves toggling up to the commiters
I was planning on a deployment process like:
issue deploy command
"build" JS, by setting all values to production values (ie. IN_DEVELOPMENT = False)
Minify JS
Deploy code to production servers
I was thinking of just using sed or something to do the IN_DEVELPMENT = False replacement. I have looked at some of the popular minification tools and none seem to offer this sort of functionality.
I would assume that this is a pretty common problem for applications. How is it usually handled? Any help would be appreciated. Thank you
I recently read an article on hackernews from mozilla:
In the Mozilla Persona code base, we frequently expose difficult to
test private functions to the public interface, clearly marking the
extra functions as part of a test API. While other developers are
still able to call these private functions, the author’s intentions
are clear.
...
publicFunction: function() {
return "publicFunction can be invoked externally but "
+ privateFunction();
}
// BEGIN TESTING API
,
privateFunction: privateFunction
// END TESTING API
};
// privateFunction is now accessible via the TESTING API
function privateFunction() {
...
Code between the // BEGIN TESTING API and //END TESTING API
pseudo-markers can be removed for production during the build process.
So other companies are definitely doing this. Are there premade tools to facilitate the JS build proccess that can remove this code? I glanced at a number of their projects on github and didn't see any. Thank you
We are using dojo
And in dojo you can use conditional exclusions for the build version of your js in order to exclude parts of your code that you would not want in your build js. Hope this helps.
eg:
var API_URL_ROOT = 'http://api.example.com/';
//>>excludeStart("dev",!pragmas.dev);
var IN_DEVELOPMENT = True;
//>>excludeEnd("dev");
if (IN_DEVELOPMENT) {
API_URL_ROOT = 'http://localhost.com/api';
}
$.get(API_URL_ROOT)

Categories

Resources