By one trick or another I managed to handle headers in both new windows (window.open, target=blank etc) and iframes with SlimerJS. It is also possible to change navigator data (such as navigator.appVersion) for new windows, but I'm stuck with doing this for iframes. It looks like the onInitialized method works correcrtly only for main window and new windows, but not for iframes.
Here is some parts of the code.
var headers =
{
"Accept-Language" : "test_language",
"Accept" : "test/accpet",
"Connection" : "keep-alive",
"Keep-Alive" : "333",
"Accept-Charset" : "test-utf",
"User-Agent" : "test_ua"
}
var webpage = require('webpage').create(
{
pageSettings:
{
javascriptEnabled: true,
loadImages: true,
loadPlugins: true,
}
});
_onPageCreated = function(childPage)
{
console.log('a new window is opened');
childPage.settings.userAgent = options.useragent["navigator.userAgent"];
childPage.customHeaders = headers;
childPage.viewportSize = { width:400, height: 500 };
childPage.onInitialized = _onInitialized;
childPage.onNavigationRequested = _onNavigationRequested;
childPage.onLoadStarted = _onLoadStarted;
childPage.onLoadFinished = _onLoadFinished;
childPage.onResourceRequested = _onResourceRequested;
childPage.onPageCreated = _onPageCreated;
};
_onResourceRequested = function(requestData, networkRequest)
{
for(var h in headers)
{
networkRequest.setHeader(h, headers[h], false);
}
...
}
var _onInitialized = function()
{
this.customHeaders = headers;
console.log("[webpage.onInitialized]");
this.evaluate(function(options)
{
(function()
{
window.navigator.__defineGetter__('appVersion', function ()
{
return options.useragent["navigator.appVersion"];
});
...
})();
}, options);
};
...
webpage.onInitialized = _onInitialized;
webpage.onNavigationRequested = _onNavigationRequested;
webpage.onLoadFinished = _onLoadFinished;
webpage.onResourceRequested = _onResourceRequested;
webpage.onPageCreated = _onPageCreated;
I tried to do this with PhantomJS, but it seems like it is not possible to use "this" instead of "webpage" in the following case:
var _onInitialized = function()
{
console.log("[webpage.onInitialized]");
this.evaluate(function() // <<---not working
{
});
console.log("test"); //is not being printed
};
I would like this function to work with "this" object, so that it could be defined as onInitialized for both main page and child pages.
Anyway, the main question is about the SlimerJS code, not the Phantom one.
Related
I'll try to be as short as possible since there's no need for code - I put an iframe tag in my Electron app and I want JavaScript to retrieve some information from it but due to same-origin policy, I can't do that. Can I disable it? Note: I am the owner of the page in the iframe.
var validate = document.getElementById('validate');
validate.onclick = function() {
var input = document.getElementById('serial').value;
var validator = document.createElement('iframe');
validator.src = "http://**********.com/?wvkey=" + input;
body.appendChild(validator);
var status = validator.contentWindow.document.getElementById('status').innerHTML;
if (status === "Key Status: Active") {
window.location.assign("../dashboard/index.html");
}
else {
window.location.assign("../support/keys/invalid/index.html");
}
}
When clicking a button on the page, the code is supposed to check for a text with the value of "Key Status: Active" in the iframe. If the condition is true, it should redirect the user to another page. If false, it should redirect him to an error page. In my app, however, the iframe just appears in the background and doesn't do anything - no redirects.
main.js (part of it):
win = new BrowserWindow({
height: 700,
resizable: false,
show: false,
webPreferences: {
nodeIntegration: true,
webSecurity: false
},
width: 790
})
EDIT (Updated)
var validate = document.getElementById('validate');
validate.onclick = function() {
var input = document.getElementById('serial').value;
var validator = document.createElement('iframe');
validator.onload = function() {
var status = validator.contentWindow.document.getElementById('status').innerHTML;
if (status === "Key Status: Active") {
window.location.assign("../dashboard/index.html");
}
else {
window.location.assign("../support/keys/invalid/index.html");
}
};
validator.src = "http://awebsite.com/?wvkey=" + input;
body.appendChild(validator);
}
Since your question is how to disable security features like CORS in an Electron window, here is the setting you can use:
win = new BrowserWindow({
webPreferences: {
webSecurity: false
}
});
I'd however only use this as a last resort and rather have your website send the appropriate CORS headers.
* Edit *
After seeing your source code I believe there is an issue with your code:
var validator = document.createElement('iframe');
validator.src = "http://**********.com/?wvkey=" + input;
This will load your website into the <iframe> object however this happens asynchronously. Means you need to wait for the page to finish loading before you can continue with:
var status = validator.contentWindow.document.getElementById('status').innerHTML;
Change your code to something like this:
var validator = document.createElement('iframe');
validator.onload = function() {
var status = validator.contentWindow.document.getElementById('status').innerHTML;
[...]
};
validator.src = "http://**********.com/?wvkey=" + input;
body.appendChild(validator);
Note that you will still need the webSecurity or CORS settings to be able to access the validator frame content.
I have a side menu and when it's open, the body can be partially seen. My side menu might be long so you could scroll on it. But when the menu is at the bottom you then scroll on the body, and I don't want this behaviour.
Similar to Scrolling only content div, others should be fixed but I'm using React. Other content should be scrollable when my side menu is closed. Think of the content as side menu in the example in the link. So far I'm using the same technique provided by that answer but it's ugly (kinda jQuery):
preventOverflow = (menuOpen) => { // this is called when side menu is toggled
const body = document.getElementsByTagName('body')[0]; // this should be fixed when side menu is open
if (menuOpen) {
body.className += ' overflow-hidden';
} else {
body.className = body.className.replace(' overflow-hidden', '');
}
}
// css
.overflow-hidden {
overflow-y: hidden;
}
What should I do with Reactjs?
You should make a meta component in react to change things on the body as well as changing things like document title and things like that. I made one a while ago to do that for me. I'll add it here.
Usage
render() {
return (
<div>
<DocumentMeta bodyClasses={[isMenuOpen ? 'no-scroll' : '']} />
... rest of your normal code
</div>
)
}
DocumentMeta.jsx
import React from 'react';
import _ from 'lodash';
import withSideEffect from 'react-side-effect';
var HEADER_ATTRIBUTE = "data-react-header";
var TAG_NAMES = {
META: "meta",
LINK: "link",
};
var TAG_PROPERTIES = {
NAME: "name",
CHARSET: "charset",
HTTPEQUIV: "http-equiv",
REL: "rel",
HREF: "href",
PROPERTY: "property",
CONTENT: "content"
};
var getInnermostProperty = (propsList, property) => {
return _.result(_.find(propsList.reverse(), property), property);
};
var getTitleFromPropsList = (propsList) => {
var innermostTitle = getInnermostProperty(propsList, "title");
var innermostTemplate = getInnermostProperty(propsList, "titleTemplate");
if (innermostTemplate && innermostTitle) {
return innermostTemplate.replace(/\%s/g, innermostTitle);
}
return innermostTitle || "";
};
var getBodyIdFromPropsList = (propsList) => {
var bodyId = getInnermostProperty(propsList, "bodyId");
return bodyId;
};
var getBodyClassesFromPropsList = (propsList) => {
return propsList
.filter(props => props.bodyClasses && Array.isArray(props.bodyClasses))
.map(props => props.bodyClasses)
.reduce((classes, list) => classes.concat(list), []);
};
var getTagsFromPropsList = (tagName, uniqueTagIds, propsList) => {
// Calculate list of tags, giving priority innermost component (end of the propslist)
var approvedSeenTags = {};
var validTags = _.keys(TAG_PROPERTIES).map(key => TAG_PROPERTIES[key]);
var tagList = propsList
.filter(props => props[tagName] !== undefined)
.map(props => props[tagName])
.reverse()
.reduce((approvedTags, instanceTags) => {
var instanceSeenTags = {};
instanceTags.filter(tag => {
for(var attributeKey in tag) {
var value = tag[attributeKey].toLowerCase();
var attributeKey = attributeKey.toLowerCase();
if (validTags.indexOf(attributeKey) == -1) {
return false;
}
if (!approvedSeenTags[attributeKey]) {
approvedSeenTags[attributeKey] = [];
}
if (!instanceSeenTags[attributeKey]) {
instanceSeenTags[attributeKey] = [];
}
if (!_.has(approvedSeenTags[attributeKey], value)) {
instanceSeenTags[attributeKey].push(value);
return true;
}
return false;
}
})
.reverse()
.forEach(tag => approvedTags.push(tag));
// Update seen tags with tags from this instance
_.keys(instanceSeenTags).forEach((attr) => {
approvedSeenTags[attr] = _.union(approvedSeenTags[attr], instanceSeenTags[attr])
});
instanceSeenTags = {};
return approvedTags;
}, []);
return tagList;
};
var updateTitle = title => {
document.title = title || document.title;
};
var updateBodyId = (id) => {
document.body.setAttribute("id", id);
};
var updateBodyClasses = classes => {
document.body.className = "";
classes.forEach(cl => {
if(!cl || cl == "") return;
document.body.classList.add(cl);
});
};
var updateTags = (type, tags) => {
var headElement = document.head || document.querySelector("head");
var existingTags = headElement.querySelectorAll(`${type}[${HEADER_ATTRIBUTE}]`);
existingTags = Array.prototype.slice.call(existingTags);
// Remove any duplicate tags
existingTags.forEach(tag => tag.parentNode.removeChild(tag));
if (tags && tags.length) {
tags.forEach(tag => {
var newElement = document.createElement(type);
for (var attribute in tag) {
if (tag.hasOwnProperty(attribute)) {
newElement.setAttribute(attribute, tag[attribute]);
}
}
newElement.setAttribute(HEADER_ATTRIBUTE, "true");
headElement.insertBefore(newElement, headElement.firstChild);
});
}
};
var generateTagsAsString = (type, tags) => {
var html = tags.map(tag => {
var attributeHtml = Object.keys(tag)
.map((attribute) => {
const encodedValue = HTMLEntities.encode(tag[attribute], {
useNamedReferences: true
});
return `${attribute}="${encodedValue}"`;
})
.join(" ");
return `<${type} ${attributeHtml} ${HEADER_ATTRIBUTE}="true" />`;
});
return html.join("\n");
};
var reducePropsToState = (propsList) => ({
title: getTitleFromPropsList(propsList),
metaTags: getTagsFromPropsList(TAG_NAMES.META, [TAG_PROPERTIES.NAME, TAG_PROPERTIES.CHARSET, TAG_PROPERTIES.HTTPEQUIV, TAG_PROPERTIES.CONTENT], propsList),
linkTags: getTagsFromPropsList(TAG_NAMES.LINK, [TAG_PROPERTIES.REL, TAG_PROPERTIES.HREF], propsList),
bodyId: getBodyIdFromPropsList(propsList),
bodyClasses: getBodyClassesFromPropsList(propsList),
});
var handleClientStateChange = ({title, metaTags, linkTags, bodyId, bodyClasses}) => {
updateTitle(title);
updateTags(TAG_NAMES.LINK, linkTags);
updateTags(TAG_NAMES.META, metaTags);
updateBodyId(bodyId);
updateBodyClasses(bodyClasses)
};
var mapStateOnServer = ({title, metaTags, linkTags}) => ({
title: HTMLEntities.encode(title),
meta: generateTagsAsString(TAG_NAMES.META, metaTags),
link: generateTagsAsString(TAG_NAMES.LINK, linkTags)
});
var DocumentMeta = React.createClass({
propTypes: {
title: React.PropTypes.string,
titleTemplate: React.PropTypes.string,
meta: React.PropTypes.arrayOf(React.PropTypes.object),
link: React.PropTypes.arrayOf(React.PropTypes.object),
children: React.PropTypes.oneOfType([
React.PropTypes.object,
React.PropTypes.array
]),
bodyClasses: React.PropTypes.array,
},
render() {
if (Object.is(React.Children.count(this.props.children), 1)) {
return React.Children.only(this.props.children);
} else if (React.Children.count(this.props.children) > 1) {
return (
<span>
{this.props.children}
</span>
);
}
return null;
},
});
DocumentMeta = withSideEffect(reducePropsToState, handleClientStateChange, mapStateOnServer)(DocumentMeta);
module.exports = DocumentMeta;
This component could probably be changed a little for your case (withSideEffect is used for both client and server side rendering... if you arent using server side rendering then its probably not completely necessary) but the component will work on client side rendering if you would like to use it there as well.
ReactJS doesn't have direct access to the <body> element, and that's the element that needs to have its overflow-y style changed. So while what you're doing isn't perhaps the prettiest code, it's not entirely wrong either.
The only real suggestion I'd give is (shudder) using inline styles on the body instead of a classname so as to avoid having to introduce the CSS declaration. As long as your menu is the only thing responsible for updating the overflow-y attribute, there's no reason you can't use an inline style on it. Mashing that down with the ?: operator results in fairly simple code:
body.style.overflowY = menuOpen ? "hidden" : "";
And then you can just delete the .overflow-hidden class in its entirety.
If for some reason multiple things are managing the overflow state of the body, you might want to stick with classnames and assign a unique one for each thing managing it, something like this:
if (menuOpen) {
body.className += ' menu-open';
}
else {
// Use some tricks from jQuery to remove the "menu-open" class more elegantly.
var className = " " + body.className + " ";
className = className.replace(" overflow-hidden ", " ").replace(/\s+/, " ");
className = className.substr(1, className.length - 2);
}
CSS:
body.menu-open {
overflow-y: hidden;
}
I'm new to ace-editor and I have included custom mode to validate my code and every line should end up with semicolon, If semicolon is not present in my query by mistake then the editor should gives up the warning like "Missing Semicolon".
define('ace/mode/javascript-custom', [], function(require, exports, module) {
var oop = require("ace/lib/oop");
var TextMode = require("ace/mode/text").Mode;
var Tokenizer = require("ace/tokenizer").Tokenizer;
var ExampleHighlightRules = require("ace/mode/example_highlight_rules").ExampleHighlightRules;
var Mode = function() {
this.HighlightRules = ExampleHighlightRules;
};
oop.inherits(Mode, TextMode);
(function() {
this.lineCommentStart = "--";
this.blockComment = {
start: "->",
end: "<-"
};
}).call(Mode.prototype);
exports.Mode = Mode;
});
define('ace/mode/example_highlight_rules', [], function(require, exports, module) {
var oop = require("ace/lib/oop");
var TextHighlightRules = require("ace/mode/text_highlight_rules").TextHighlightRules;
var ExampleHighlightRules = function() {
var keywordMapper = this.createKeywordMapper({
"variable.language": "this",
"keyword": "one|two",
"constant.language": "true|false|null"
}, "text", true);
this.$rules = {
"start": [{
token: "comment",
regex: "->",
next: [{
regex: "<-",
token: "comment",
next: "start"
}, {
defaultToken: "comment"
}]
}, {
regex: "\\w+\\b",
token: keywordMapper
}, {
token: "comment",
regex: "--.*"
}, {
token: "string",
regex: '"',
next: [{
regex: /\\./,
token: "escape.character"
}, {
regex: '"',
token: "string",
next: "start"
}, {
defaultToken: "string"
}]
}, {
token: "numbers",
regex: /\d+(?:[.](\d)*)?|[.]\d+/
}]
};
this.normalizeRules()
};
oop.inherits(ExampleHighlightRules, TextHighlightRules);
exports.ExampleHighlightRules = ExampleHighlightRules;
});
var langTools = ace.require("ace/ext/language_tools");
var editor = ace.edit("editor");
editor.session.setMode("ace/mode/javascript-custom");
editor.setOptions({
enableBasicAutocompletion: true,
enableLiveAutocompletion: true
});
editor.setTheme("ace/theme/monokai");
var lines = editor.session.doc.getAllLines();
var errors = [];
for (var i = 0; i < lines.length; i++) {
if (/[\w\d{(['"]/.test(lines[i])) {
alert("hello");
errors.push({
row: i,
column: lines[i].length,
text: "Missing Semicolon",
type: "error"
});
}
}
<script src="https://ajaxorg.github.io/ace-builds/src/ext-language_tools.js"></script>
<script src="https://ajaxorg.github.io/ace-builds/src/ace.js"></script>
<div id="editor" style="height: 200px; width: 400px"></div>
<div id="commandline" style="position: absolute; bottom: 10px; height: 20px; width: 800px;"></div>
UPDATE:
The following js files are generated from ace and added to my rails application, the files are loaded in rails app but the functionality (semicolon check) doesn't seem to be working.
worker-semicolonlineend - http://pastebin.com/2kZ2fYr9
mode-semicolonlineend - http://pastebin.com/eBY5VvNK
Update:
In ace editor, type in a query1, query2 in line 1 and line 2 respectively
Leave the third line blank
Now in fourth line, type a query without semicolon in the end, x mark appears in third line
5 And when the fifth line is also without a semicolon, then the x mark is displayed at fourth query
Ace editor widely support this kind analysis for JavaScript by default:
#editor {
position: absolute;
top: 0;
right: 0;
bottom: 0;
left: 0;
}
<div id="editor">function foo() { ; // unnessesary semicolon
var x = "bar" // missing semicolon
return x; // semicolon in place
}
</div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/ace/1.1.9/ace.js" type="text/javascript"></script>
<script>
var editor = ace.edit("editor");
editor.setTheme("ace/theme/monokai");
editor.getSession().setMode("ace/mode/javascript");
</script>
Just make sure that worker file worker-javascript.js is available for your code. In code snippet above I use CDN to get Ace build, so worker is always available. You can configure JSHint via worker options.
Update: But if really need something beyond that you will need to do the following as my understanding goes:
Create Worker and Mode for you kind of analysis
Download Ace source code and install NodeJS
Put your new files within correspond Ace source code folders
Build Ace
Add build files to your project
Use new mode: editor.getSession().setMode("ace/mode/semicolonlineend");
Worker that perform line ending check will look something like that:
define(function(require, exports, module) {
"use strict";
var oop = require("../lib/oop");
var Mirror = require("../worker/mirror").Mirror;
var SemicolonLineEndCheckWorker = exports.SemicolonLineEndCheckWorker = function (sender) {
Mirror.call(this, sender);
this.setTimeout(500);
this.setOptions();
};
oop.inherits(SemicolonLineEndCheckWorker, Mirror);
(function() {
this.onUpdate = function () {
var text = this.doc.getValue();
var lines = text.replace(/^#!.*\n/, "\n").match(/[^\r\n]+/g);
var errors = [];
for (var i = 0; i < lines.length; i++) {
var lastLineCharacter = lines[i].trim().slice(-1);
if (lastLineCharacter === ';')
continue;
errors.push({
row: i,
column: lines[i].length-1,
text: "Missing semicolon at the end of the line",
type: "warning",
raw: "Missing semicolon"
});
}
this.sender.emit("annotate", errors);
};
}).call(SemicolonLineEndCheckWorker.prototype);
});
New mode that uses worker:
define(function(require, exports, module) {
"use strict";
var oop = require("../lib/oop");
var TextMode = require("./text").Mode;
var Mode = function() { };
oop.inherits(Mode, TextMode);
(function() {
this.createWorker = function(session) {
var worker = new WorkerClient(["ace"], "ace/mode/semicolonlineend_worker",
"SemicolonLineEndCheckWorker");
worker.attachToDocument(session.getDocument());
worker.on("annotate", function(results) {
session.setAnnotations(results.data);
});
worker.on("terminate", function() {
session.clearAnnotations();
});
return worker;
};
this.$id = "ace/mode/semicolonlineend";
}).call(Mode.prototype);
exports.Mode = Mode;
});
I am experimenting with meteor and I'm facing some code structuring issue.
What I want:
I use an observer to keep track of new document added on collection, but i want to be 'notified' only after the the template is fully rendered.
In my router.js file i have:
HospitalListController = RouteController.extend({
action: function() {
Meteor.subscribe('hospitals');
this.render('listHospitals');
}
});
My client listHospital.js file is
Template.listHospitals.onRendered(function(){
var first = true;
hospitalsCursor = Hospitals.find();
var totals = hospitalsCursor.count();
var loaded = 0;
HospitalsHandle = hospitalsCursor.observeChanges({
added : function(doc){
if( loaded != totals ){
loaded++;
}else{
console.log("added "+doc);
}
}
});
});
Is there a better way, maybe a 'meteor-way' to accomplish that?
You have to add a flag to ignore observeChange callback during initialization (found this solution here).
Template.listHospitals.onRendered(function(){
var initialized = false;
hospitalsCursor = Hospitals.find();
HospitalsHandle = hospitalsCursor.observeChanges({
added : function(doc){
if(initialized) {
// your logic
}
}
});
initialized = true;
});
This should work.
My actual working code:
Template.queueView.onRendered(function(){
var initialLoaded = false;
Queues.find().observeChanges({
added : function(id, doc){
if( initialLoaded ){
console.log("added "+id);
highlight();
beep();
}
},
removed : function(id, doc){
console.log("removed "+id);
highlight();
beep();
},
changed : function(){
console.log('modified!');
highlight();
beep();
}
});
Meteor.subscribe('queues', function(){
initialLoaded = true;
});
});
I would like to switch to an iframe using pure phantom.js code
Here is my first attempt
var page = new WebPage();
var url = 'http://www.theurltofectch'
page.open(url, function (status) {
if ('success' !== status) {
console.log("Error");
} else {
page.switchToFrame("thenameoftheiframe");
console.log(page.content);
phantom.exit();
}
});
It produces only the source code of the main page. Any idea ?
Notice that the iframe domain is different from the main page domain.
Please give this a try I believe it may be an async issues meaning the iframe is not present when trying to access it. I received the below snippet from another post.
var page = require('webpage').create(),
testindex = 0,
loadInProgress = false;
page.onConsoleMessage = function(msg) {
console.log(msg);
};
page.onLoadStarted = function() {
loadInProgress = true;
console.log("load started");
};
page.onLoadFinished = function() {
loadInProgress = false;
console.log("load finished");
};
/*
page.onNavigationRequested = function(url, type, willNavigate, main) {
console.log('Trying to navigate to: ' + url);
console.log('Caused by: ' + type);
console.log('Will actually navigate: ' + willNavigate);
console.log('Sent from the page\'s main frame: ' + main);
};
*/
/*
The steps array represents a finite set of steps in order to perform the unit test
*/
var steps = [
function() {
//Load Login Page
page.open("https://www.yourpage.com");
},
function() {
//access your iframe here
page.evaluate(function() {
});
},
function() {
//any other step you want
page.evaluate(function() {
});
},
function() {
// Output content of page to stdout after form has been submitted
page.evaluate(function() {
//console.log(document.querySelectorAll('html')[0].outerHTML);
});
//render a test image to see if login passed
page.render('test.png');
}
];
interval = setInterval(function() {
if (!loadInProgress && typeof steps[testindex] === "function") {
console.log("step " + (testindex + 1));
steps[testindex]();
testindex++;
}
if (typeof steps[testindex] !== "function") {
console.log("test complete!");
phantom.exit();
}
}, 50);
replace
console.log(page.content);
with
console.log(page.frameContent);
Should return the contents of the frame phantomjs switched to.
If the iframe is from another domain you may need to add the --web-security=no option like this:
phantomjs --web-security=no myscript.js
As an additional information, what xMythicx said could be true. Some iframes are rendered via Javascript after page finishes loading. If the iframe contents are empty, then you will need to wait for all resources to finish loading, before you start grabbing stuff from the page. But this is another issue, if you need an answer on this, I suggest you ask a new question about it, and I will answer there.
Had the same problem for iframes and
phantomjs --web-security=no
helped in my case :]