I'm building a Safari extension with two different content scripts. One script needs to be injected into all http pages (but not https pages). The other one only gets injected into google.com pages regardless of scheme.
In order to achieve this, I have set Extension Website Access to:
This should mean that at a high level, content scripts in my extension should be able to access all pages.
To get more fine-grained control, I then programatically inject the content scripts into URLs which match my patterns.
App = {
content: {
// Inject into unsecure pages.
whitelist: ['http://*/*'],
// But not into secure pages.
blackList: ['https://*/*'],
loc: safari.extension.baseURI + 'data/content.js'
},
results: {
// Inject this script into all google.com pages
whiteList: ['http://*.google.com/*', 'https://*.google.com/*'],
// Surely I don't need a blacklist if I specify a whitelist?
blacklist: undefined,
loc: safari.extension.baseURI + 'data/results.js',
}
};
// Inject the first content script.
safari.extension.addContentScriptFromURL(App.content.loc,
App.content.whitelist, App.content.blacklist, false);
// Inject the second content script.
safari.extension.addContentStyleSheetFromURL(App.results.cssLoc,
App.results.whitelist, App.results.blacklist, false);
The problem is that both scripts are being injected into all pages. It's as if my white and blacklists do nothing. What am I doing wrong?
I was using capitals in my whilelist/blacklist definitions at the top:
App = {
content: {
blackList: ['https://*/*'],
},
results: {
whiteList: ['http://*.google.com/*', 'https://*.google.com/*']
}
};
But then using non-capitalized versions of the variables when I pass the lists into the script injection function.
safari.extension.addContentScriptFromURL(App.content.loc, App.content.whitelist, App.content.blacklist, false);
This obviously means that undefined was being passed into the injection function rather than an actual whitelist/blacklist.
Related
Good day.
Problem definition:
I would like to deploy my Vue app, that utilizes vue-router, on dynamic path, which should be controlled by the WebServer, utilize vue-router in history mode, and avoid re-packaging the application for each Deployment.
F.e.
Run same app at
http://localhost/subpath/index.html
and
http://localhost/another-subpath/index.html
As vue-router configuration is done at the packaging stage (f.e. by webpack), and thus is not designed to be controlled after the packaging stage, making this simple but so-common setup not viable.
Also, vue-router have quite a complicated lifecycle, which does not allow to easily override that base setting at the application-level.
In addition - webpack ends up hardcoding actual resources into the html body, which ensures their proper consumption by the Client at the load-time, but as chunks are inter-dependent - they are almost impossible to be injected/edited dynamically. Application integrity fails in case of any modification to those on-post-DomReady event.
Research:
My search so far have not yielded any viable options to set such configuration up.
I came up with a couple of viable solutions.
One of the problems is that changing the Router Base is not the only thing one needs, in order to dynamically change the App's root, and make it function properly on dynamic URL.
In case of vue+webpack - actual scripts are added to the index.html on-build-stage, and thus - again - end up being hardcoded.
There are few options of fixing that, which I came up with.
The Ugly solution:
Regex-replace resources URLs at the index.html, on the web-server level, and hardcoded setting for your vue-router-base.
This is very questionable approach, but viable.
Unfortunately I do not have examples of that approach left, so - I can't provide any, but this should be pretty straightforward:
On your servlet-side, Pseudocode:
let fileContent = getFileContent(requested_file_name);
if (servingFile == "index.html") {
fileContent = fileContent.regexReplace("http://url_hardcoded_at_packaging_stage", "http://url_application_is_deployed_at");
fileContent = fileContent.regexReplace("setting_token_in_your_index_html", "http://url_application_is_deployed_at");
}
return fileContent;
In addition to that you will need to add this setting_token_in_your_index_html in your index.html:
<head>
<script type="text/javascript">
window.my_app_settings = {routerBase:"setting_token_in_your_index_html"}
</script>
</head>
And consume it at the vue-router level:
export default new Router({
mode: "history",
base: window.my_app_settings.routerBase,
routes: [...]
...
});
This approach is good in a sense that there is no need to modify anything on your vue-app level, and all the changes can be kept only on your servlet-side, whatever it is.
Also it has 0 performance impact on the vue-app itself.
Still ugly, but a lots better, from code-perspective at least:
Make vue-app base-aware.
This solution is not super-simple, but works reliably on all major web-servers and can be summarized as
Dynamically add vue-resources at the app init, based on cookie provided by the Web-Server.
This approach allows to "elegantly" modify all the resources URLs, with minimum impact on the performance-side, and keep things as dynamic as they can be.
This approach consists of few small changes on the Packaging, and the app-levels, and also relies on cookie to inform the app about custom base.
Provide the custom URL cookie from Server:
At your web-server add the cookie header (all major web-servers support such functionality):
(f.e. in Scalatra)
val contextShiftCookie = "subpath_where_ui_deployed";
val cookie = new Cookie("ui_deployment_root", contextShiftCookie );
cookie.setPath("/");
response.addCookie( cookie );
App modifications (index.html):
At the <head> section:
<script type="text/javascript">
// Build script names, for later injection.
let stringsJs = [
<% for (let js in htmlWebpackPlugin.files.js) { %>
"<%= htmlWebpackPlugin.files.js[js] %>",
<% } %>
];
let stringsCss = [
<% for (let css in htmlWebpackPlugin.files.css) { %>
"<%= htmlWebpackPlugin.files.css[css] %>",
<% } %>
];
// Simple vanilla Cookie getter (replace with something else if needed).
function getCookie(cname) {
var name = cname + "=";
var decodedCookie = decodeURIComponent(document.cookie);
var ca = decodedCookie.split(';');
for(var i = 0; i < ca.length; i++) {
var c = ca[i];
while (c.charAt(0) == ' ') {
c = c.substring(1);
}
if (c.indexOf(name) == 0) {
return c.substring(name.length, c.length);
}
}
return "";
}
// Simple vanilla onDomReady handler (replace with something else if needed).
(function(exports, d) {
function domReady(fn, context) {
function onReady(event) {
d.removeEventListener("DOMContentLoaded", onReady);
fn.call(context || exports, event);
}
function onReadyIe(event) {
if (d.readyState === "complete") {
d.detachEvent("onreadystatechange", onReadyIe);
fn.call(context || exports, event);
}
}
d.addEventListener && d.addEventListener("DOMContentLoaded", onReady) ||
d.attachEvent && d.attachEvent("onreadystatechange", onReadyIe);
}
exports.domReady = domReady;
})(window, document);
// Calculating vue router base.
let routerBase = "/";
if (getCookie("ui_deployment_root")) {
routerBase = getCookie("ui_deployment_root");
// console.log("Found base cookie", routerBase);
} else {
console.log("No base cookie found");
}
let scriptsBase = routerBase == "/"? "" : routerBase;
// Prefilling basic settings.
window.my_vue_app_config = {
routerBase: routerBase
};
function loadOneScript(){
if (window.my_vue_app_scripts.length > 0) {
let currentScript = window.my_vue_app_scripts.shift();
currentScript.onload = loadOneScript;
document.getElementsByTagName("body").item(0).append(currentScript);
}
}
// Injecting scripts using deployment context.
domReady(function(event) {
let head = document.getElementsByTagName("head").item(0);
stringsCss.forEach(css => {
let script = document.createElement("link");
script.setAttribute("rel","stylesheet");
script.setAttribute("href", scriptsBase + css);
head.append(script);
});
window.my_vue_app_scripts = [];
stringsJs.forEach(js => {
let script = document.createElement("script");
script.setAttribute("type","text/javascript");
script.setAttribute("src", scriptsBase + js);
window.my_vue_app_scripts.push(script);
});
loadOneScript();
});
</script>
Step-by-step explanation:
Consume base setting as-soon-as-it-is-available, BEFORE actual chunks are loaded, to ensure that vue-router can consume it nicely.
Gather all the script names from WebPack (see let stringsJs and let stringsCss)
Load all the css as soon as the DomReady (because css is consumed on-the-fly, and there is nothing specific about loading them. They just should be available). See stringsCss.forEach for that.
Load all the js chunks one-by-one, as this is the only way to ensure that webpack-ed app will be initialized properly (see stringsJs.forEach, and loadOneScript routine).
Scripts loading is done in such manner that each script (chunk) requests another chunk as soon as it is loaded and consumed by the Client (browser). This ensures app integrity, and proper initialization, regardless of how it was packed.
Chunks are processed in proper order, provided by the webpack, again - to ensure integrity.
vue-router changes:
Consume new setting at the router-level:
export default new Router({
mode: "history",
base: window.my_vue_app_config.routerBase,
routes: [...]
...
});
Webpack mechanism changes:
To support custom bundling at the webpack:
<% for (let js in htmlWebpackPlugin.files.js) { %>
"<%= htmlWebpackPlugin.files.js[js] %>",
<% } %>
, you will need to modify the /app/build/webpack.ENV.conf.js file:
...
new HtmlWebpackPlugin({
...
inject: false,
...
}),
Where .ENV. should be your desired bundle target, but I suggest changing this for all your environments, as then you will be sure that your index.html changes work properly on all Environments.
Performance considerations:
This was my biggest concern with this "sequential scripts injection", because it sounds very bad, from performance perspective.
But, to my biggest surprise, actual tests have shown only ~0.100-0.150ms raize in the JavaScript processing time, compared to the fully-static serve of the Application, according to tests on all major Web Clients (browsers).
This basically renders performance concern irrelevant in my case, but we wary that this might differ in your case.
Other impact on the application lifecycle is totally neglectable, at least in my case (less than 0.05ms in overall impact on the App Load).
P.S.
As this approach is something I've developed purely myself - I would appreciate constructive criticism, and improvement proposals :)
I'm trying to find elements in iframe but it doesn't work.
Is there anyone who have some system to run tests with Cypress in iframe? Some way to get in iframe and work in there.
It's a known issue mentioned here. You can create your own custom cypress command which mocks the iframe feature. Add following function to your cypress/support/commands.js
Cypress.Commands.add('iframe', { prevSubject: 'element' }, ($iframe, selector) => {
Cypress.log({
name: 'iframe',
consoleProps() {
return {
iframe: $iframe,
};
},
});
return new Cypress.Promise(resolve => {
resolve($iframe.contents().find(selector));
});
});
Then you can use it like this:
cy.get('#iframe-id')
.iframe('body #elementToFind')
.should('exist')
Also, because of CORS/same-origin policy reasons, you might have to set chromeWebSecurity to false in cypress.json (Setting chromeWebSecurity to false allows you to access cross-origin iframes that are embedded in your application and also navigate to any superdomain without cross-origin errors).
This is a workaround though, it worked for me locally but not during CI runs.
This works for me locally and via CI. Credit: Gleb Bahmutov iframes blog post
export const getIframeBody = (locator) => {
// get the iframe > document > body
// and retry until the body element is not empty
return cy
.get(locator)
.its('0.contentDocument.body').should('not.be.empty')
// wraps "body" DOM element to allow
// chaining more Cypress commands, like ".find(...)"
// https://on.cypress.io/wrap
.then(cy.wrap)
}
spec file:
let iframeStripe = 'iframe[name="stripe_checkout_app"]'
getIframeBody(iframeStripe).find('button[type="submit"] .Button-content > span').should('have.text', `Buy me`)
that is correct. Cypress doesn't support Iframes. It is simple not possible at the moment. You can follow (and upvote) this ticket: https://github.com/cypress-io/cypress/issues/136
I'm using the Enplug SDK web extension to create an app to show digital menus from DSMenu on screens using Enplug. I'm using AngularJS on my config page.
I'm confused about the relationship between the "Configure Url" setting in the back-end from this guide, and the Value.Url in the payload from this tutorial.
Configure URL
Value.Url
$scope.page = {
Value: {
ShowContent: 'url', // Show Content is used to hide/show the Url or Html form field based on the selection.
Url: '', // The Url the web page back-end uses to display the content.
Html: '', // If applicable, used to show custom HTML. Cannot be used in conjunction with the Url.
ShowMobileWebsite: false, //Only applies if OverrideUserAgent is true, False = Show Desktop Website, True = Show Mobile Website
OverrideUserAgent: false, //False = Use android's best fit. True = Use the value of ShowMobileWebsite
ShowDelay: 0, //Custom delay between displaying the page after it's been loaded.
RefreshInterval: 0, // Custom refresh interval rate in X seconds.
AllowJavascript: true, // Set to true by default, allows Javascript to be executed on the page.
Username: '', // Username option, would need to write script passing in credentials.
Password: '', // Password option, would need to write script passing in credentials.
Token: '', // Token option, would need to wrtie script passing in credentials.
JavascriptOnload: '' // Custom JS to be executed once the page loads, can be used to log into authenticated pages.
}
};
I created a page http://www.dsmenu.com/con-enplug-display.php and each app will have a custom URL to show the menu like http://www.dsmenu.com/uph/204. Where do I put each?
The 'Configure Url' is the link to the configuration page which will be displayed to the end user on Enplugs web dashboard, in your example http://www.dsmenu.com/con-enplug-display.php
The Value.Url is the link to the web app that will be shown publicly on the screen in the venue. In your case http://www.dsmenu.com/uph/204
The website I want to test has a landing page which asks you to chose a language. You can skip this question by adding an extra parameter in the url. I want to test this behaviour with Geb and Spock as testing framework.
So I have the landing page with language selection:
class LanguageSelectionPage extends Page {
static url = "http://localhost:8080/registration/"
static at = { $("form#languageForm") }
}
The second page where it redirects to:
class InsertCardReaderPage extends Page {
static at = { welcomeTitle1 }
static content = {
welcomeTitle1(wait: true, cache: false) { $("#eidWelcomeTitle1") }
welcomeTitle2(wait: true, cache: false) { $("#eidWelcomeTitle2") }
}
}
(I've removed some methods from the pasted code)
So this is my test:
given:
to LanguageSelectionPage, "09";
expect:
at InsertCardReaderPage;
The "09" is an extra parameter in the url, when this one is available you will be immediatly redirected by the server (http redirect, so the page does change) to the InsertCardReaderPage. Now, my problem is that the to statement performs an implicit assertion on the at closure. This one fails because you have been redirected away from the page already.
Is there a way to conditionally disable this implicit assertion in this case? Or any other proposal how to setup the pages? I'm pretty new to Geband can't find any documentation that seems to help me in this case.
Use via instead of to
given:
via LanguageSelectionPage, "09";
expect:
at InsertCardReaderPage;
Geb Manual
While building my first mobile app using sencha touch 2 some questions got in my way and I can't seem to find their answer.
Where should an app configuration be stored (theme, language, font size ). I was thinking
to count the data from a store and if bigger than 0 work on that data otherwise add data( this would happen only the first time application is opened or localstorage cleared..). There are other options for this kind of thing(things like an array which will be changed when user is interacting with the app) ?
I need to use in my application around 100 images. I don't know what options I have here to embed the images into app. Saw lots of examples loading image from external server but not sure if there is an option for packing them with the app.
If I had an array with a name(key) and the image url(value), where should this array be ? in a json file and use an ajax load each time a need a name in there ?
Thanks.
Let me suggest few options:
1- App configuration : If app configuration is like set of constant values which won't change by user interaction you can create a file (e.g. properties.js) and load it on application load.
Properties = {
SERVICE_URL : 'http://mycompany.com/api',
PAGE_SIZE : 20
}
and to load it you just have to edit app.json
"js": [
{
"path": "touch/sencha-touch.js",
"x-bootstrap": true
},
{
"path": "resources/data/properties.js"
}
]
If you want to control these values then you can keep it on your server and give its URL as "path" in app.json
2- There is always option of packaging images with your app, just like all the icon & startup images are packaged but its not suggested because it increases size of your deployable and people with slow internet connections and low end devices might skip installing it if size it too large.
3- No need to load the JSON file every time you need it, you can cache the data in global variable after first load and keep referring to the array whenever required. Now where to define global variable is another interesting discussion with people suggesting lot of things but I prefer to have a singleton class which can keep all the global functions & variables. See this thread to understand how : Where do I put my global helper functions if they are needed before Ext.application() is being executed?
For Text we can Try like this
var A_address=Ext.getCmp('address').getValue(); //get the value
localStorage.setItem("Adult1_select1",A_select1); // assign localstore
var web_arrayTotalPAssengers=[];
web_arrayTotalPAssengers.push(localStorage.getItem("web_TotalPassengers"));
console.log(web_arrayTotalPAssengers);
// push the values in array...
Ext.Ajax.request({
url:'http:/...........',
method:'POST',
disableCaching: false,
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json'
},
jsonData: {
origin:Ext.decode(web_arrayTotalPAssengers), //decode and send
}
success:function(response)
{
console.log(response);
console.log("Success");
},
failure : function(response)
{
console.log("Failed");
}