I am building an extension in which content script is injected on context menu click. This functionality is working. Now the problem I am facing is that, the first click on context menu is not working.
Now the problem I am facing is that, the first click on context menu is not working. Is this a bug?.
background.js
on_message = async(message, sender, sendResponse) => {
console.log("bg.on_message");
sendResponse("from bg");
chrome.storage.local.get("list_url", function (data) {
if (typeof data.list_url != "undefined") {
urls = data.list_url
}
});
chrome.storage.local.get("list_ip", function (data) {
if (typeof data.list_ip != "undefined") {
ips = data.list_ip
}
});
chrome.storage.local.get("list_hash", function (data) {
if (typeof data.list_hash != "undefined") {
hashes = data.list_hash;
}
});
if (hashes){
hash_report = await createHashReport(hashes)
hash_table = await createHashTable(hash_report)
await chrome.storage.local.set({
"scanHash": true,
"hash_table": hash_table
}, () => {});
}
if (ips){
ip_report = await createIpReport(ips)
ip_table = await createIpTable(ip_report)
await chrome.storage.local.set({
"scanIp": true,
"ip_table": ip_table
}, () => {});
}
if (urls){
url_report = await createUrlReport(urls)
url_table = await createUrlTable(url_report)
await chrome.storage.local.set({
"scanUrl": true,
"url_table": url_table
}, () => {});
}
if ( hashes.length>0 || urls.length>0 || ips.length>0 ){
chrome.windows.create({url: "output.html", type: "popup", height:1000, width:1000});
}
}
chrome.runtime.onMessage.addListener(on_message);
genericOnClick = async () => {
// Inject the payload.js script into the current tab after the backdround has loaded
chrome.tabs.query({ active: true, currentWindow: true }, function (tabs) {
chrome.scripting.executeScript({
target: { tabId: tabs[0].id },
files: ["payload.js"]
},() => chrome.runtime.lastError);
});
}
// create context menu
chrome.runtime.onInstalled.addListener(() => {
chrome.contextMenus.create({
id: 'scrapper',
"title": "Scrapper",
"contexts": ["all"]
});
});
chrome.contextMenus.onClicked.addListener(genericOnClick);
payload.js
function extract() {
htmlInnerText = document.documentElement.innerText;
url_exp = /[-a-zA-Z0-9#:%_\+.~#?&//=]{2,256}\.[a-z]{2,4}\b(\/[-a-zA-Z0-9#:%_\+.~#?&//=]*)?/gi;
regex = new RegExp(url_exp)
list_url = htmlInnerText.match(url_exp)
ip_exp = /\b\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}\b/;
list_ip = htmlInnerText.match(ip_exp)
hash_exp = /\b[A-Fa-f0-9]{32}\b|\b[A-Fa-f0-9]{40}\b|\b[A-Fa-f0-9]{64}\b/g
list_hash = htmlInnerText.match(hash_exp)
await chrome.storage.local.set({ list_url: removeEmails(removeDuplicates(list_url)), list_ip: removeDuplicates(list_ip), list_hash: removeDuplicates(list_hash) });
}
chrome.runtime.sendMessage( extract());
Assuming that "removeEmails" and "removeDuplicates" are synchronous functions (only you know this), the chrome.storage.local.set method instead returns a promise. Basically you send a message to the background hoping that it can read the updated values of list_url, list_ip and list_hash, when instead it has those available before the storage.local.set (so it won't find anything on the first round).
I thought you'd already figured it out yourself with my clue.
Add await keyword before each chrome.storage.local.set
I'm trying to make a custom prompt for Quill.js, but it does not work.
I need to get a value from a prompt and pass it to Quill handler.
Here is an example for test: https://jsfiddle.net/yk03dt7j/
function promptInput(callback, event) {
let prompt = document.getElementById('prompt');
if(prompt) {
let input = document.getElementById('input');
prompt.style.display = 'block';
document.getElementById("ok").onclick = function() {
prompt.style.display = 'none';
callback(input.value);
};
}
}
function videoHandler() {
promptInput(function(value) {
console.log(value);
});
let range = this.quill.getSelection();
this.quill.insertEmbed(range.index, 'video', value);
this.quill.setSelection(range.index + 1);
}
var quill = new Quill('#editor', {
modules: {
toolbar: {
container: "#toolbar",
handlers: {
video: videoHandler
}
}
},
placeholder: 'Content',
theme: 'snow'
});
I've tried this way (does not work):
function videoHandler() {
promptInput(function(value) {
let range = this.quill.getSelection();
this.quill.insertEmbed(range.index, 'video', value);
this.quill.setSelection(range.index + 1);
});
}
I refactored your code a little:
var prompt = document.getElementById('prompt');
var input = document.getElementById('input');
var okButton = document.getElementById("ok");
okButton.onclick = () => {
prompt.style.display = 'none';
embedVideo();
};
const embedVideo = () => {
let range = this.quill.getSelection();
this.quill.insertEmbed(range.index, 'video', input.value);
this.quill.setSelection(range.index + 1);
}
function videoHandler() {
if (prompt) prompt.style.display = 'block';
}
var quill = new Quill('#editor', {
modules: {
toolbar: {
container: "#toolbar",
handlers: {
video: videoHandler
}
}
},
placeholder: 'Content',
theme: 'snow'
});
Here is the working example: https://jsfiddle.net/zj70u2kr/15/
The documentation has both Events and EventListeners. I can get the EventListeners to fire but the Events do not have adequate documentation for me to get going. What is the difference and how do you use? Thank you.
https://github.com/airbnb/lottie-web#events
Events (Do not work, how to use?)
// From the Documentation
onComplete
onLoopComplete
onEnterFrame
onSegmentStart
you can also use addEventListener with the following events:
complete
loopComplete
enterFrame
segmentStart
config_ready (when initial config is done)
data_ready (when all parts of the animation have been loaded)
data_failed (when part of the animation can not be loaded)
loaded_images (when all image loads have either succeeded or errored)
DOMLoaded (when elements have been added to the DOM)
destroy
// End Documentation
From the standard addEventListener usage, this works...
birbSequence.addEventListener('loopComplete', (e) => {
console.log(e);
});
although 'complete' does not fire.
But to try out the stuff in Events like onEnterFrame?
var birbSequence = lottie.loadAnimation({
container: bodyMovinContainer1,
loop: true,
renderer: 'svg',
path: 'Birb Sequence 1.json',
onEnterFrame: function(e) { console.log(e); }
});
I am really new to using Lottie though so could use some help.
Just want a way to see how to use Events
Let's say we have our lottie animation:
const anim = lottie.loadAnimation({
container: '#container',
renderer: 'svg',
loop: true,
autoplay: true,
...
})
With Events:
anim.onComplete = function() {
console.log('complete')
}
anim.onLoopComplete = function() {
console.log('loopComplete')
}
With addEventListener:
anim.addEventListener('complete', function() {
console.log('complete')
})
anim.addEventListener('loopComplete', function() {
console.log('loopComplete')
})
You can use the addEventListener method to listen to all the events instead of the on* series of event hooks.
const options = {
container: '#container',
loop: false,
autoplay: false,
renderer: 'svg',
rendererSettings: {
scaleMode: 'noScale',
clearCanvas: false,
progressiveLoad: true,
hideOnTransparent: true,
},
};
try {
const anim = lottie.loadAnimation({ ...options, path: 'URL_TO_JSON' });
anim.addEventListener('complete', () => { console.log('complete'); });
anim.addEventListener('loopComplete', () => { console.log('loopComplete'); });
anim.addEventListener('data_ready ', () => { console.log('data_ready'); });
anim.addEventListener('data_failed', () => { console.log('data_failed'); });
anim.addEventListener('enterFrame', () => {
console.log('enterFrame', anim.currentFrame);
});
// etc ...
} catch (error)
console.log('error loading anim');
}
Hope that helps!
I am hoping you can help. I am fairly new to Unit Testing. I have a Karma + Jasmine set up which is running a PhantomJS browser. This is all good.
What I am struggling with is I have a link on the page, when this link is clicked it injects some HTML. I want to test that the HTML has been injected.
Now at this point, I have the test working but only sometimes, from what I can figure out if my JS runs fast enough the HTML gets injected before the expect() is run. If not the test fails.
How can I make my Jasmine test wait for all JS to finish executing before the expect() is run?
The test in question is it("link can be clicked to open a modal", function() {
modal.spec.js
const modalTemplate = require('./modal.hbs');
import 'regenerator-runtime/runtime';
import 'core-js/features/array/from';
import 'core-js/features/array/for-each';
import 'core-js/features/object/assign';
import 'core-js/features/promise';
import Modal from './modal';
describe("A modal", function() {
beforeAll(function() {
const data = {"modal": {"modalLink": {"class": "", "modalId": "modal_1", "text": "Open modal"}, "modalSettings": {"id": "", "modifierClass": "", "titleId": "", "titleText": "Modal Title", "closeButton": true, "mobileDraggable": true}}};
const modal = modalTemplate(data);
document.body.insertAdjacentHTML( 'beforeend', modal );
});
it("link exists on the page", function() {
const modalLink = document.body.querySelector('[data-module="modal"]');
expect(modalLink).not.toBeNull();
});
it("is initialised", function() {
spyOn(Modal, 'init').and.callThrough();
Modal.init();
expect(Modal.init).toHaveBeenCalled();
});
it("link can be clicked to open a modal", function() {
const modalLink = document.body.querySelector('[data-module="modal"]');
modalLink.click();
const modal = document.body.querySelector('.modal');
expect(modal).not.toBeNull();
});
afterAll(function() {
console.log(document.body);
// TODO: Remove HTML
});
});
EDIT - More Info
To further elaborate on this, The link Jasmine 2.0 how to wait real time before running an expectation put in the comments has helped me understand a bit better, I think. So what we are saying it we want to spyOn the function and wait for it to be called and then initiate a callback which then resolves the test.
Great.
My next issue is, if you look at the structure of my ModalViewModel class below, I need to be able to spyOn insertModal() to be able to do this, but the only function that is accessible in init(). What would I do to be able to move forward with this method?
import feature from 'feature-js';
import { addClass, removeClass, hasClass } from '../../01-principles/utils/classModifiers';
import makeDraggableItem from '../../01-principles/utils/makeDraggableItem';
import '../../01-principles/utils/polyfil.nodeList.forEach'; // lt IE 12
const defaultOptions = {
id: '',
modifierClass: '',
titleId: '',
titleText: 'Modal Title',
closeButton: true,
mobileDraggable: true,
};
export default class ModalViewModel {
constructor(module, settings = defaultOptions) {
this.options = Object.assign({}, defaultOptions, settings);
this.hookModalLink(module);
}
hookModalLink(module) {
module.addEventListener('click', (e) => {
e.preventDefault();
this.populateModalOptions(e);
this.createModal(this.options);
this.insertModal();
if (this.options.closeButton) {
this.hookCloseButton();
}
if (this.options.mobileDraggable && feature.touch) {
this.hookDraggableArea();
}
addClass(document.body, 'modal--active');
}, this);
}
populateModalOptions(e) {
this.options.id = e.target.getAttribute('data-modal');
this.options.titleId = `${this.options.id}_title`;
}
createModal(options) {
// Note: As of ARIA 1.1 it is no longer correct to use aria-hidden when aria-modal is used
this.modalTemplate = `<section id="${options.id}" class="modal ${options.modifierClass}" role="dialog" aria-modal="true" aria-labelledby="${options.titleId}" draggable="true">
${options.closeButton ? '<a href="#" class="modal__close icon--cross" aria-label="Close" ></a>' : ''}
${options.mobileDraggable ? '<a href="#" class="modal__mobile-draggable" ></a>' : ''}
<div class="modal__content">
<div class="row">
<div class="columns small-12">
<h2 class="modal__title" id="${options.titleId}">${options.titleText}</h2>
</div>
</div>
</div>
</section>`;
this.modal = document.createElement('div');
addClass(this.modal, 'modal__container');
this.modal.innerHTML = this.modalTemplate;
}
insertModal() {
document.body.appendChild(this.modal);
}
hookCloseButton() {
this.closeButton = this.modal.querySelector('.modal__close');
this.closeButton.addEventListener('click', (e) => {
e.preventDefault();
this.removeModal();
removeClass(document.body, 'modal--active');
});
}
hookDraggableArea() {
this.draggableSettings = {
canMoveLeft: false,
canMoveRight: false,
moveableElement: this.modal.firstChild,
};
makeDraggableItem(this.modal, this.draggableSettings, (touchDetail) => {
this.handleTouch(touchDetail);
}, this);
}
handleTouch(touchDetail) {
this.touchDetail = touchDetail;
const offset = this.touchDetail.moveableElement.offsetTop;
if (this.touchDetail.type === 'tap') {
if (hasClass(this.touchDetail.eventObject.target, 'modal__mobile-draggable')) {
if (offset === this.touchDetail.originY) {
this.touchDetail.moveableElement.style.top = '0px';
} else {
this.touchDetail.moveableElement.style.top = `${this.touchDetail.originY}px`;
}
} else if (offset > this.touchDetail.originY) {
this.touchDetail.moveableElement.style.top = `${this.touchDetail.originY}px`;
} else {
this.touchDetail.eventObject.target.click();
}
} else if (this.touchDetail.type === 'flick' || (this.touchDetail.type === 'drag' && this.touchDetail.distY > 200)) {
if (this.touchDetail.direction === 'up') {
if (offset < this.touchDetail.originY) {
this.touchDetail.moveableElement.style.top = '0px';
} else if (offset > this.touchDetail.originY) {
this.touchDetail.moveableElement.style.top = `${this.touchDetail.originY}px`;
}
} else if (this.touchDetail.direction === 'down') {
if (offset < this.touchDetail.originY) {
this.touchDetail.moveableElement.style.top = `${this.touchDetail.originY}px`;
} else if (offset > this.touchDetail.originY) {
this.touchDetail.moveableElement.style.top = '95%';
}
}
} else {
this.touchDetail.moveableElement.style.top = `${this.touchDetail.moveableElementStartY}px`;
}
}
removeModal() {
document.body.removeChild(this.modal);
}
static init() {
const instances = document.querySelectorAll('[data-module="modal"]');
instances.forEach((module) => {
const settings = JSON.parse(module.getAttribute('data-modal-settings')) || {};
new ModalViewModel(module, settings);
});
}
}
UPDATE
After working through it has been discovered that .click() events are asynchronous which is why I am gettnig the race issue. Documentation & Stack Overflow issues thoughtout the web recommend using createEvent() and dispatchEvent() as PhantomJs does not understand new MouseEvent().
Here is my code which is now trying to do this.
modal.spec.js
// All my imports and other stuff
// ...
function click(element){
var event = document.createEvent('MouseEvent');
event.initMouseEvent('click', true, true, window, 0, 0, 0, 0, 0, false, false, false, false, 0, null);
element.dispatchEvent(event);
}
describe("A modal", function() {
// Some other tests
// Some other tests
it("link can be clicked to open a modal", function() {
const modalLink = document.body.querySelector('[data-module="modal"]');
click(modalLink);
const modal = document.body.querySelector('.modal');
expect(modal).not.toBeNull();
});
// After all code
// ...
});
Unfortunately this is producting the same results. 1 step closer but not quite there.
After a touch of research, it looks as though your use of the click event is triggering an asynchronous event loop essentially saying "Hey set this thing to be clicked and then fire all the handlers"
Your current code can't see that and has no real way of waiting for it. I do believe you should be able to build and dispatch a mouse click event using the info here.
https://developer.mozilla.org/en-US/docs/Web/API/EventTarget/dispatchEvent
I think that should allow you to build a click event and dispatch it onto your element. The difference is that dispatchEvent is synchronous - it should block your test until the click handlers have completed. That should allow you to do your assertion without failures or race conditions.
I have finally found a solution.
There are 2 parts to this, the first part came from #CodyKnapp. His insight into a click() function running asynchronously helped to solve the first part of the issue.
Here is the code for this part.
modal.spec.js
// All my imports and other stuff
// ...
function click(element){
var event = document.createEvent('MouseEvent');
event.initMouseEvent('click', true, true, window, 0, 0, 0, 0, 0, false, false, false, false, 0, null);
element.dispatchEvent(event);
}
describe("A modal", function() {
// Some other tests
// Some other tests
it("link can be clicked to open a modal", function() {
const modalLink = document.body.querySelector('[data-module="modal"]');
click(modalLink);
const modal = document.body.querySelector('.modal');
expect(modal).not.toBeNull();
});
// After all code
// ...
});
This allowed for the code to run synchronously.
The second part was a poor understanding on my part of how to write Jasmine tests. In my original tests I was running Modal.init() inside of it("is initialised", function() { when actually I want to be running this inside of beforeAll(). This fixed the issue I had where my tests would not always be successful.
Here is my final code:
modal.spec.js
const modalTemplate = require('./modal.hbs');
import '#babel/polyfill';
import Modal from './modal';
function click(element){
var event = document.createEvent('MouseEvent');
event.initMouseEvent('click', true, true, window, 0, 0, 0, 0, 0, false, false, false, false, 0, null);
element.dispatchEvent(event);
}
describe("A modal", function() {
beforeAll(function() {
const data = {"modal": {"modalLink": {"class": "", "modalId": "modal_1", "text": "Open modal"}, "modalSettings": {"id": "", "modifierClass": "", "titleId": "", "titleText": "Modal Title", "closeButton": true, "mobileDraggable": true}}};
const modal = modalTemplate(data);
document.body.insertAdjacentHTML( 'beforeend', modal );
spyOn(Modal, 'init').and.callThrough();
Modal.init();
});
it("link exists on the page", function() {
const modalLink = document.body.querySelector('[data-module="modal"]');
expect(modalLink).not.toBeNull();
});
it("is initialised", function() {
expect(Modal.init).toHaveBeenCalled();
});
it("link can be clicked to open a modal", function() {
const modalLink = document.body.querySelector('[data-module="modal"]');
click(modalLink);
const modal = document.body.querySelector('.modal');
expect(modal).not.toBeNull();
});
afterAll(function() {
console.log(document.body);
// TODO: Remove HTML
});
});
Codepen
I am trying to add a custom button on the toolbar so that once a user is done editing they can remove the text area.
var init = false;
var quill = null;
document.getElementById('editor-container').addEventListener('click', () => {
console.log(quill);
if(!init) {
quill = new Quill('#editor-container', {
modules: {
toolbar: '#toolbar-container'
},
placeholder: 'Compose an epic...',
theme: 'snow' // or 'bubble'
});
init = true;
};
var Parchment = Quill.import("parchment");
let CustomClass = new Parchment.Attributor.Class('custom', 'ql-custom', {
scope: Parchment.Scope.INLINE
});
Quill.register(CustomClass, true);
var customButton = document.querySelector('#custom-button');
customButton.addEventListener('click', function(event) {
event.stopPropagation();
quill = null;
console.log(quill);
});
})