requirejs load script progress - requirejs

Risking of asking a stupid question I will give it a try though.
Is it possible to display a progress indicator while RequireJs is loading dependencies?
For example:
require(['jquery'], function($) {
// Well, jQuery loaded in quite some time due to a low-speed connection
//
// Or maybe I wanted to show an overlay to prevent user of clicking on UI widgets until the load is complete
});
I don't want to start modifying the RequireJS source if there's some plugin out there which didn't show up in my Google searches.
Thanks for all your help.

It is possible to display a spinner or progress indicator, but not the bar itself. I could get status of requirejs (currently loading or idle), but not the % of loaded/needed to be loaded modules, because their dependencies are parsed upon every module load, but not at the beginning.
But, anyway, page with, at least, a simple spinner, is much better, than just blank page, while user waits.
No changes to requirejs needed! So..
Assume we have file require.conf.js with
require.config({...})
require(["app"], function () {
// main entry point to your hugde app's code
});
and it is loaded by html using
<script data-main="require.conf" type="text/javascript" src="bower_components/requirejs/require.js"></script>
This is standard requirejs scenario. Let's add the indicator to the
<div id="requirejs-loading-panel">
<div id="requirejs-loading-status"></div>
<div id="requirejs-loading-module-name"></div>
</div>
Ok, let's catch up requirejs's function called require.onResourceLoad and do all the magic needed. It will be called by requirejs upon every module load, passing the requirejs's context with dep tree and all other staff. We will use context to find out, whether requirejs is loading something or not. I did it in scheduled timer call, because onResourceLoad() is called only while loading, not when it is done loading. This code needs to be added to require.js.conf:
require.onResourceLoad = function (context, map, depMaps) {
var loadingStatusEl = document.getElementById('requirejs-loading-status'),
loadingModuleNameEl = document.getElementById('requirejs-loading-module-name');
var panel = document.getElementById('requirejs-loading-panel');
if (loadingStatusEl && loadingModuleNameEl) {
if (!context) { // we well call onResourceLoad(false) by ourselves when requirejs is not loading anything => hide the indicator and exit
panel.style.display = "none";
return;
}
panel.style.display = ""; // show indicator when any module is loaded and shedule requirejs status (loading/idle) check
clearTimeout(panel.ttimer);
panel.ttimer = setTimeout(function () {
var context = require.s.contexts._;
var inited = true;
for (name in context.registry) {
var m = context.registry[name];
if (m.inited !== true) {
inited = false;
break;
}
} // here the "inited" variable will be true, if requirejs is "idle", false if "loading"
if (inited) {
require.onResourceLoad(false); // will fire if module loads in 400ms. TODO: reset this timer for slow module loading
}
}, 400)
if (map && map.name) { // we will add one dot ('.') and a currently loaded module name to the indicator
loadingStatusEl.innerHTML = loadingStatusEl.innerHTML += '.'; //add one more dot character
loadingModuleNameEl.innerHTML = map.name + (map.url ? ' at ' + map.url : '');
}
} else {
}
};
One problem is: we cannot somehow figure out how much modules are needed to load, so we can't compute the actual % of loading progress. But, at least, we can find out, whether we're loading something or not (and event get currently loading module name) and show it to a nervous user

Since v. 2.1.19 RequireJS has onNodeCreated config property. If you assign a function to it, that function will be called each time a <script> element is appended to a document to load a module.
The function will be provided with node, config, moduleName, and url arguments. By attaching event listeners to the node, you can detect when the script has been loaded or failed to be loaded.
Now you can detect when loading process starts and stops and can use that information to notify users:
require.config({
/* ... paths, shims, etc. */
onNodeCreated: function(node, config, moduleName, url) {
console.log('module ' + moduleName + ' is about to be loaded');
node.addEventListener('load', function() {
console.log('module ' + moduleName + ' has been loaded');
});
node.addEventListener('error', function() {
console.log('module ' + moduleName + ' could not be loaded');
});
},
});
For IE < 9 you'd need additional code to attach event listeners.
Note: this only works for modules requested by RequireJS directly. Plugins (requirejs/text and such) each use their own loading mechanism and do not trigger onNodeCreated. Some trigger onXhr and onXhrComplete though, so you can handle them too:
require.config({
/* ... paths, shims, etc. */
config: {
text: {
onXhr: function(xhr, url) {
console.log('file ' + url + ' is about to be loaded');
},
onXhrComplete: function(xhr, url) {
console.log('file ' + url + ' has been loaded');
}
}
}
});

No, this isn't possible. RequrieJs loads the module by creating a new script tag in the DOM and listens to the load event. There are no update events in between start and end loading. So it's not a limitation of requireJs but the DOM.

Related

instagram embed doesn't work at first when dynamically appending into a page, but then works after refresh

Go to:
http://staging2.ju.blog.kylebaker.io/
click hamburger
click 'timeline'
instagram embeds show only the gray logo and don't load fully. embed.js doesn't seem to load when watching in the network tab.
Now, click refresh.
Now, everything loads. embed.js is there.
:/
You can notice that an older version on http://staging.ju.blog.kylebaker.io works fine--this seems to obviously be because it's an entirely new page load (which I want to avoid).
Some potentially relevant code this theme relies on to load this page "into" the page:
L: function(url, f, err) {
if (url == xhrUrl) {
return false;
}
xhrUrl = url;
if (xhr) {
xhr.abort();
}
xhr = $.ajax({
type: 'GET',
url: url,
timeout: 10000,
success: function(data) {
f(data);
xhrUrl = '';
},
error: function(a, b, c) {
if (b == 'abort') {
err && err()
} else {
window.location.href = url;
}
xhrUrl = '';
}
});
},
HS: function(tag, flag) {
var id = tag.data('id') || 0,
url = tag.attr('href'),
title = tag.attr('title') + " - " + $("#config-title").text();
if (!$('#preview').length || !(window.history && history.pushState)) location.href = url;
Diaspora.loading()
var state = {d: id, t: title, u: url};
Diaspora.L(url, function(data) {
if (!$(data).filter('#single').length) {
location.href = url;
return
}
switch (flag) {
case 'push':
history.pushState(state, title, url)
break;
case 'replace':
history.replaceState(state, title, url)
break;
}
document.title = title;
$('#preview').html($(data).filter('#single'))
switch (flag) {
case 'push':
Diaspora.preview()
break;
case 'replace':
window.scrollTo(0, 0)
Diaspora.loaded()
break;
}
setTimeout(function() {
Diaspora.player();
$('#top').show();
comment = $("#gitalk-container");
if (comment.data('ae') == true){
comment.click();
}
}, 0)
})
},
preview: function() {
// preview toggle
$("#preview").one('transitionend webkitTransitionEnd oTransitionEnd otransitionend MSTransitionEnd', function() {
var previewVisible = $('#preview').hasClass('show');
if (!!previewVisible) {
$('#container').hide();
}else{
$('#container').show();
}
Diaspora.loaded();
});
setTimeout(function() {
$('#preview').addClass('show');
$('#container').data('scroll', window.scrollY);
setTimeout(function() {
$('#preview').css({
'position': 'static',
'overflow-y': 'auto'
});
}, 500);
}, 0);
},
(for full file, see: https://github.com/Fechin/hexo-theme-diaspora/blob/master/source/js/diaspora.js)
I see the script tag loaded in the DOM; why isn't it loading? Feels like it must be something simple I'm missing... It's 4am and I've been working non-stop.
(please ignore the dust, this is a small side-project work-in-progress with many small things broken.)
Things I've tried:
adding the code to load embed.js manually in the page. (no change--I then see embed.js is loaded, but it doesn't have an impact on the result)
editing the URL to include "http:" before the "//insta..." url (as
recommended in some answers elsewhere for some people) (no change)
window.instgrm.Embeds.process() it seems the instgrm object no longer exists. (no change)
Seems to be related to however this is being injected via jquery, but I'm a little confused as to specifics, figured I'd ask the world and make a space for the answer to exist for the next poor soul.
Note that because of what I've tried, the answers here do not seem to be helpful or relevant, though maybe it's just me blanking at 4am.
The problem was, indeed, the things I had already addressed, I just missed some rendering details of getting changes to stick. jQuery executing and stripping the script tags seems to have been the source the problem, and calling window.instgrm.Embeds.process() at the appropriate time, after making sure that the embeds.js script was requested in the right place/at the right time, was enough to fix the issue seen above. Part of the confusion was using hexo, which uses ejs in node, which doesn't really seem to allow client-executing inline JS in ejs template files themselves, silently.

EventEmitter memory leak detected: Proper way to pass CSV data to multiple modules?

I am dipping my toe into using different npm modules my own way whereas before I just executed already created gulpfiles. The npm module penthouse loads a webpage and determines the above the fold CSS for that page. I am trying to take that module and use it with a site crawler so I can get the above the fold css for all pages, and store that CSS in a table.
So essentially I am:
Crawling a site to get all the urls
capturing the page id from each url
storing pages & their id's in a CSV
load the CSV and pass each URL to penthouse
take penthouse output and store it in a table
So I am fine up until the last two steps. When I am reading the CSV, I get the error possible EventEmitter memory leak detected. 11 exit listeners added. Use emitter.setMaxListeners() to increase limit.
The stack trace points here at line 134. After reading about the error, it makes sense because I see a bunch of event listeners being added, but I don't see penthouse ever really executing and closing the event listeners.
It works just fine standalone as expected (Running penthouse against a single page then exiting). But when I execute the code below to try and loop through all URLs in a csv, it spits out the memory leak error twice, and just hangs. None of my console.log statements in the following script are executed.
However, I added console.log to the end of the penthouse index.js file, and it is executed multiple times (where it adds event listeners), but it never timeouts or exits.
So it's clear I am not integrating this properly, but not sure how to proceed. What would be the best way to force it to read one line in the CSV at a time, process the URL, then take the output and store it in the DB before moving onto the next line?
const fs = require('fs');
var csv = require('fast-csv');
var penthouse = require('penthouse'),
path = require('path');
var readUrlCsv = function() {
var stream = fs.createReadStream("/home/vagrant/urls.csv");
var csvStream = csv()
//returns single line from CSV
.on("data", function(data) {
// data[0]: table id, data[1]: page type, data[2]: url
penthouse({
url : data[2],
css : './dist/styles/main.css'
}, function(err, criticalCss) {
if (err) {
console.log(err);
}
console.log('do we ever get here?'); //answer is no
if (data[1] === 'post') {
wp.posts().id( data[0] ).post({
inline_css: criticalCss
}).then(function( response ) {
console.log('saved to db');
});
} else {
wp.pages().id( data[0] ).page({
inline_css: criticalCss
}).then(function( response ) {
console.log('saved to db');
});
}
});
})
.on("end", function(){
console.log("done");
});
return stream.pipe(csvStream);
};
UPDATE
Changed my method to look like below so it processes all rows first, but still throws the same error. Writes "done" to the console, and immediately spits out the memory warning twice.
var readUrlCsv = function() {
var stream = fs.createReadStream("/home/vagrant/urls.csv");
var urls = [];
var csvStream = csv()
.on("data", function(data) {
// data[0]: table id, data[1]: page type, data[2]: url
urls.push(data);
})
.on("end", function(){
console.log("done");
buildCriticalCss(urls);
});
return stream.pipe(csvStream);
};
var buildCriticalCss = function(urls) {
//console.log(urls);
urls.forEach(function(data, idx) {
//console.log(data);
penthouse({
url : data[2],
css : './dist/styles/main.css',
// OPTIONAL params
width : 1300, // viewport width
height : 900, // viewport height
timeout: 30000, // ms; abort critical css generation after this timeout
strict: false, // set to true to throw on css errors (will run faster if no errors)
maxEmbeddedBase64Length: 1000 // charaters; strip out inline base64 encoded resources larger than this
}, function(err, criticalCss) {
if (err) {
console.log(err);
}
console.log('do we ever finish one?');
if (data[1] === 'post') {
console.log('saving post ' + data[0]);
wp.posts().id( data[0] ).post({
inline_css: criticalCss
}).then(function( response ) {
console.log('saved post to db');
});
} else {
console.log('saving page ' + data[0]);
wp.pages().id( data[0] ).page({
inline_css: criticalCss
}).then(function( response ) {
console.log('saved page to db');
});
}
});
});
};
Update 2
I took the simple approach to control the amount of concurrent processes spawned.
var readUrlCsv = function() {
var stream = fs.createReadStream("/home/vagrant/urls.csv");
var urls = [];
var csvStream = csv()
.on("data", function(data) {
// data[0]: table id, data[1]: page type, data[2]: url
urls.push(data);
})
.on("end", function(){
console.log("done");
//console.log(urls);
buildCriticalCss(urls);
});
return stream.pipe(csvStream);
};
function buildCriticalCss(data) {
var row = data.shift();
console.log(row);
penthouse({
url : row[2],
css : './dist/styles/main.css',
// OPTIONAL params
width : 1300, // viewport width
height : 900, // viewport height
timeout: 30000, // ms; abort critical css generation after this timeout
strict: false, // set to true to throw on css errors (will run faster if no errors)
maxEmbeddedBase64Length: 1000 // charaters; strip out inline base64 encoded resources larger than this
}, function(err, criticalCss) {
if (err) {
console.log('err');
}
// handle your criticalCSS
console.log('finished');
console.log(row[2]);
// now start next job, if we have more urls
if (data.length !== 0) {
buildCriticalCss(data);
}
});
}
The error message you're seeing is a default printed to the console by node's event library if more than the allowed number of event listeners are defined for an instance of EventEmitter. It does not indicate an actual memory leak. Rather it is displayed to make sure you're aware of the possibility of a leak.
You can see this by checking the event.EventEmitter source code at lines 20 and 244.
To stop EventEmitter from displaying this message and since penthouse does not expose its specific EventEmitter, you'll need to set the default allowed event emitter listeners to something larger than its default value of 10 using:
var EventEmitter=require('event').EventEmitter;
EventEmitter.defaultMaxListeners=20;
Note that according to Node's documentation for EventEmitter.defaultMaxListeners, this will change the maximum number of listeners for all instances of EventEmitter, including those that have already been defined previous to the change.
Or you could simply ignore the message.
Further to the hanging of your code, I'd advise gathering all the results from the parsing of your CSV into an array, and then processing the array contents separately from the parsing process.
This would accomplish two things: It would allow you to
be assured the entire CSV file was valid before you started processing, and
instrument debugging messages while processing each element, which would give you deeper insight into how each element of the array was processed.
UPDATE
As noted below, depending on how many URLs you're processing, you're probably overwhelming Node's ability to handle all of your requests in parallel.
One easy way to proceed would be to use eventing to marshall your processing so your URLs are processed sequentially, as in:
var assert=require('assert'),
event=require('events'),
fs=require('fs'),
csv=require('fast-csv');
penthouse=require('penthouse');
var emitter=new events.EventEmitter();
/** Container for URL records read from CSV file.
*
* #type {Array}
*/
var urls=[];
/** Reads urls from file and triggers processing
*
* #emits processUrl
*/
var readUrlCsv = function() {
var stream = fs.createReadStream("/home/vagrant/urls.csv");
stream.on('error',function(e){ // always handle errors!!
console.error('failed to createReadStream: %s',e);
process.exit(-1);
});
var csvStream = csv()
.on("data", function(data) {
// data[0]: table id, data[1]: page type, data[2]: url
urls.push(data);
})
.on("end", function(){
console.log("done reading csv");
//console.log(urls);
emitter.emit('processUrl'); // start processing URLs
})
.on('error',function(e){
console.error('failed to parse CSV: %s',e);
process.exit(-1);
});
// no return required since we don't do anything with the result
stream.pipe(csvStream);
};
/** Event handler to process a single URL
*
* #emits processUrl
*/
var onProcessUrl=function(){
// always check your assumptions
assert(Array.isArray(urls),'urls must be an array');
var urlRecord=urls.shift();
if(urlRecord){
assert(Array.isArray(urlRecord),'urlRecord must be an array');
assert(urlRecord.length>2,'urlRecord must have at least three elements');
penthouse(
{
// ...
},
function(e,criticalCss){
if(e){
console.error('failed to process record %s: %s',urlRecord,e);
return; // IMPORTANT! do not drop through to rest of func!
}
// do what you need with the result here
if(urls.length===0){ // ok, we're done
console.log('completed processing URLs');
return;
}
emitter.emit('processUrl');
}
);
}
}
/**
* processUrl event - triggers processing of next URL
*
* #event processUrl
*/
emitter.on('processUrl',onProcessUrl); // assign handler
// start everything going...
readUrlCsv();
The benefit of using events here rather than your solution is the lack of recursion which can easily overwhelm your stack.
Hint: You can use events to handle all program flow issues normally addressed by Promises or modules like async.
And since events are at the very heart of Node (the "event loop"), it's really the best, most efficient way to solve such problems.
It's both elegant and "The Node Way"!
Here is a gist that illustrates the technique, without relying on streams or penthouse, the output of which is:
url: url1
RESULT: RESULT FOR url1
url: url2
RESULT: RESULT FOR url2
url: url3
RESULT: RESULT FOR url3
completed processing URLs
Besides using console.logs which usually is enough, you can also use the built in debugger: https://nodejs.org/api/debugger.html
Another thing you can do is go into the node_modules/penthouse directory and add your console.logs or debugger statement into the code for that module. That way you can debug your program there rather than the module just being a black box.
Also make sure there isn't some kind of race condition where for example the CSV doesn't always get output before it tries to read them in.
I think that the memory leak issue is probably a red herring as far as making your code function.
From your comment it sounds like you want to do something like the following with async.mapSeries: http://promise-nuggets.github.io/articles/15-map-in-series.html You could also use promises as it shows or even after getting promises set up use the async/await stuff with a regular for loop after compiling with babel. In the long run I recommend doing that sort of thing with async/await and babel but that might be overkill just to get this working.

Chrome Extenion - chrome.tabs.executescript - how to pass a variable in the code parameter [duplicate]

How can I pass a parameter to the JavaScript in a content script file which is injected using:
chrome.tabs.executeScript(tab.id, {file: "content.js"});
There's not such a thing as "pass a parameter to a file".
What you can do is to either insert a content script before executing the file, or sending a message after inserting the file. I will show an example for these distinct methods below.
Set parameters before execution of the JS file
If you want to define some variables before inserting the file, just nest chrome.tabs.executeScript calls:
chrome.tabs.executeScript(tab.id, {
code: 'var config = 1;'
}, function() {
chrome.tabs.executeScript(tab.id, {file: 'content.js'});
});
If your variable is not as simple, then I recommend to use JSON.stringify to turn an object in a string:
var config = {somebigobject: 'complicated value'};
chrome.tabs.executeScript(tab.id, {
code: 'var config = ' + JSON.stringify(config)
}, function() {
chrome.tabs.executeScript(tab.id, {file: 'content.js'});
});
With the previous method, the variables can be used in content.js in the following way:
// content.js
alert('Example:' + config);
Set parameters after execution of the JS file
The previous method can be used to set parameters after the JS file. Instead of defining variables directly in the global scope, you can use the message passing API to pass parameters:
chrome.tabs.executeScript(tab.id, {file: 'content.js'}, function() {
chrome.tabs.sendMessage(tab.id, 'whatever value; String, object, whatever');
});
In the content script (content.js), you can listen for these messages using the chrome.runtime.onMessage event, and handle the message:
chrome.runtime.onMessage.addListener(function(message, sender, sendResponse) {
// Handle message.
// In this example, message === 'whatever value; String, object, whatever'
});
There are five general ways to pass data to a content script injected with tabs.executeScript()(MDN):
Set the data prior to injecting the script
Use chrome.storage.local(MDN) to pass the data (set prior to injecting your script).
Inject code prior to your script which sets a variable with the data (see detailed discussion for possible security issue).
Set a cookie for the domain in which the content script is being injected. This method can also be used to pass data to manifest.json content scripts which are injected at document_start, without the need for the content script to perform an asynchronous request.
Send/set the data after injecting the script
Use message passing(MDN) to pass the data after your script is injected.
Use chrome.storage.onChanged(MDN) in your content script to listen for the background script to set a value using chrome.storage.local.set()(MDN).
Use chrome.storage.local (set prior to executing your script)
Using this method maintains the execution paradigm you are using of injecting a script that performs a function and then exits. It also does not have the potential security issue of using a dynamic value to build executing code, which is done in the second option below.
From your popup script:
Store the data using chrome.storage.local.set()(MDN).
In the callback for chrome.storage.local.set(), call tabs.executeScript()(MDN).
var updateTextTo = document.getElementById('comments').value;
chrome.storage.local.set({
updateTextTo: updateTextTo
}, function () {
chrome.tabs.executeScript({
file: "content_script3.js"
});
});
From your content script:
Read the data from chrome.storage.local.get()(MDN).
Make the changes to the DOM.
Invalidate the data in storage.local (e.g. remove the key with: chrome.storage.local.remove() (MDN)).
chrome.storage.local.get('updateTextTo', function (items) {
assignTextToTextareas(items.updateTextTo);
chrome.storage.local.remove('updateTextTo');
});
function assignTextToTextareas(newText){
if (typeof newText === 'string') {
Array.from(document.querySelectorAll('textarea.comments')).forEach(el => {
el.value = newText;
});
}
}
See: Notes 1 & 2.
Inject code prior to your script to set a variable
Prior to executing your script, you can inject some code that sets a variable in the content script context which your primary script can then use:
Security issue:
The following uses "'" + JSON.stringify().replace(/\\/g,'\\\\').replace(/'/g,"\\'") + "'" to encode the data into text which will be proper JSON when interpreted as code, prior to putting it in the code string. The .replace() methods are needed to A) have the text correctly interpreted as a string when used as code, and B) quote any ' which exist in the data. It then uses JSON.parse() to return the data to a string in your content script. While this encoding is not strictly required, it is a good idea as you don't know the content of the value which you are going to send to the content script. This value could easily be something that would corrupt the code you are injecting (i.e. The user may be using ' and/or " in the text they entered). If you do not, in some way, escape the value, there is a security hole which could result in arbitrary code being executed.
From your popup script:
Inject a simple piece of code that sets a variable to contain the data.
In the callback for chrome.tabs.executeScript()(MDN), call tabs.executeScript() to inject your script (Note: tabs.executeScript() will execute scripts in the order in which you call tabs.executeScript(), as long as they have the same value for runAt. Thus, waiting for the callback of the small code is not strictly required).
var updateTextTo = document.getElementById('comments').value;
chrome.tabs.executeScript({
code: "var newText = JSON.parse('" + encodeToPassToContentScript(updateTextTo) + "');"
}, function () {
chrome.tabs.executeScript({
file: "content_script3.js"
});
});
function encodeToPassToContentScript(obj){
//Encodes into JSON and quotes \ characters so they will not break
// when re-interpreted as a string literal. Failing to do so could
// result in the injection of arbitrary code and/or JSON.parse() failing.
return JSON.stringify(obj).replace(/\\/g,'\\\\').replace(/'/g,"\\'")
}
From your content script:
Make the changes to the DOM using the data stored in the variable
if (typeof newText === 'string') {
Array.from(document.querySelectorAll('textarea.comments')).forEach(el => {
el.value = newText;
});
}
See: Notes 1, 2, & 3.
Use message passing(MDN)(send data after content script is injected)
This requires your content script code to install a listener for a message sent by the popup, or perhaps the background script (if the interaction with the UI causes the popup to close). It is a bit more complex.
From your popup script:
Determine the active tab using tabs.query()(MDN).
Call tabs.executeScript()(MDN)
In the callback for tabs.executeScript(), use tabs.sendMessage()(MDN)(which requires knowing the tabId), to send the data as a message.
var updateTextTo = document.getElementById('comments').value;
chrome.tabs.query({active: true, currentWindow: true}, function(tabs) {
chrome.tabs.executeScript(tabs[0].id, {
file: "content_script3.js"
}, function(){
chrome.tabs.sendMessage(tabs[0].id,{
updateTextTo: updateTextTo
});
});
});
From your content script:
Add a listener using chrome.runtime.onMessage.addListener()(MDN).
Exit your primary code, leaving the listener active. You could return a success indicator, if you choose.
Upon receiving a message with the data:
Make the changes to the DOM.
Remove your runtime.onMessage listener
#3.2 is optional. You could keep your code active waiting for another message, but that would change the paradigm you are using to one where you load your code and it stays resident waiting for messages to initiate actions.
chrome.runtime.onMessage.addListener(assignTextToTextareas);
function assignTextToTextareas(message){
newText = message.updateTextTo;
if (typeof newText === 'string') {
Array.from(document.querySelectorAll('textarea.comments')).forEach(el => {
el.value = newText;
});
}
chrome.runtime.onMessage.removeListener(assignTextToTextareas); //optional
}
See: Notes 1 & 2.
Note 1: Using Array.from() is fine if you are not doing it many times and are using a browser version which has it (Chrome >= version 45, Firefox >= 32). In Chrome and Firefox, Array.from() is slow compared to other methods of getting an array from a NodeList. For a faster, more compatible conversion to an Array, you could use the asArray() code in this answer. The second version of asArray() provided in that answer is also more robust.
Note 2: If you are willing to limit your code to Chrome version >= 51 or Firefox version >= 50, Chrome has a forEach() method for NodeLists as of v51. Thus, you don't need to convert to an array. Obviously, you don't need to convert to an Array if you use a different type of loop.
Note 3: While I have previously used this method (injecting a script with the variable value) in my own code, I was reminded that I should have included it here when reading this answer.
You can use the args property, see this documentation
const color = '#00ff00';
function changeBackgroundColor(backgroundColor) {
document.body.style.backgroundColor = backgroundColor;
}
chrome.scripting.executeScript(
{
target: {tabId},
func: changeBackgroundColor,
args: [color],
},
() => { ... });
Edit: My mistake - This only applies to injected functions, not files as the question specifies.
#RobW's answer is the perfect answer for this. But for you to implement this you need to initiate global variables.
I suggest an alternative for this, which is similar to #RobW's answer. Instead of passing the variable to the file, you load a function from the content.js file and then initiate the function in your current context using the code: and pass variables from current context.
var argString = "abc";
var argInt = 123;
chrome.tabs.executeScript(tabId, { file: "/content.js" }).then(() => {
chrome.tabs.executeScript(tabId, {
allFrames: false,
code: "myFunction('" + argString + "', " + argInt + "); ",
});
});
This is inspired from #wOxxOm's answer here. This method is really going to be helpful to write a common source code for Manifest v2 & v3

Define a global ready function for every page in WinJS

My WinJS app uses the single navigation model. There is some common code that I would like to apply to every page in the app. Instead of placing the code in each page's ready function, I would like to be able to able to define a "global" ready function that will be executed when a page's ready event is fired. Any ideas?
you can define a Mixin object with utility function used for all pages.
utils.js:
PageMixin = {
ready: function ready(element, options)
{
this.element = element;
this.options = options;
this.initialize();
this.onready();
},
initialize: function initialize()
{
// write common initialize code here
}
};
page.js:
var Page = WinJS.UI.Pages.define('/pages/mypage/page.html',
{
onready: function onready()
{
// page specific initialization code here
}
});
// this will make all PageMixin util methods available on Page.
WinJS.Class.mix(Page, PageMixin);
refer WinJS.Class.mixin for details.

How to pass a value from to a page in Chrome extension development?

I have a popup, call 'welcome.html', the thing I would like to do is when the user select a text, and click my plugin, it will use some of the page information, and print back to the welcome.html. For example, the web site title, and the text which the user selected and the url. But how can I pass value to that welcome.html? Thank you.
I do a lot of this in my extension as it mines a lot of data enabling the user to easily copy it to their clipboard.
Since you're looking for a lot less data it's even simpler. When your popup is being loaded you can call the following function to retrieve the information you require;
function getData(callback) {
chrome.tabs.getSelected(null, function (tab) {
var data = {
selection: '',
title: tab.title,
url: tab.url
};
/*
* We can't call content scripts on some pages and the process will get
* stuck if we try.
*/
if (tab.url.indexOf('chrome') === 0 ||
tab.url.indexOf('https://chrome.google.com/webstore') === 0) {
callback(data);
} else {
chrome.tabs.sendRequest(tab.id, {}, function (response) {
data.selection = response.selection;
callback(data);
});
}
});
}
Ensure you pass in a callback function which will be called once all the data has been extracted;
getData(function (data) {
console.log('Title: ' + data.title);
console.log('URL: ' + data.url);
console.log('Selected Text: ' + data.selection);
// Display the data instead
});
As you may have noticed the getData function sends a request to the selected tab. A content script will need to be injected in to the page in order for this to work so be sure you've configured your manifest correctly or injected it programmatically prior to calling getData or it won't work. The script that will need to be injected should resemble the following;
(function () {
chrome.extension.onRequest.addListener(function (request, sender,
sendResponse) {
sendResponse({
selection: window.getSelection().toString()
});
});
})();
This simply returns the currently selected text. One concern is that this data look-up could potentially cause a slight pause while the popup is rendered but you should test this yourself and experiment with it but there are solutions.
This should cover all you need to know so good luck and let me know if you need any further help as I'm aware this could be slightly overwhelming if you're new to Chrome extensions.

Resources