Firefox Addon SDK - how to make page in new tabs run only once - tabs

I am new to Firefox Addon SDK, high level API.
What I wanted to do it, if a user click the icon on the toolbar, a new tab is opened, and run the script defined in contentscriptfile.
I use the script below:
var self = require("sdk/self");
var tabs = require("sdk/tabs");
var buttons = require('sdk/ui/button/action');
var button = buttons.ActionButton({
id: "mm-link",
label: "Visit mm",
icon: {
"32": "./icon-32.png",
"64": "./icon-64.png"
},
onClick: handleClick
});
function handleClick(state) {
tabs.open("about:blank");
tabs.on('ready', function (tab) {
tab.attach({
contentScriptFile: self.data.url("home.js"),
contentScriptOptions: {"aaa" : "1111", "bob" : "222"}
});
});
}
But it doesn't work as expected, and has the following problems:
The script runs repeatedly. (I wanted it run only once on each new tab)
Even if I click the "+" icon to create a new tab, the script will
run. (I wanted it only run when clicking the icon I created on the toolbar)
I have also tried to change 'ready' to 'activiate', the repeated running problem is gone, but every time I create the tab, the script will run.
Many thanks to any help.

The issue is that you're listening to the ready event of any and all tabs, rather than the one you just opened. One option is to do something like this:
function handleClick(state) {
tabs.open({
url: "about:blank",
onOpen: function onOpen(tab) {
tab.attach({
contentScriptFile: self.data.url("home.js"),
contentScriptOptions: {"aaa" : "1111", "bob" : "222"}
});
}
});
}
And attach the script using the onOpen handler. For more info the docs are here: https://developer.mozilla.org/en-US/Add-ons/SDK/High-Level_APIs/tabs#open%28options%29

Related

Update Google Calendar UI after changing visability setting via Workspace Add-On

I have a very basic Google Workspace Add-on that uses the CalendarApp class to toggle the visabilty of a calendar’s events when a button is pressed, using the setSelected() method
The visabilty toggling works, but the change in only reflected in the UI when the page is refreshed. Toggling the checkbox manually in the UI reflects the change immediately without needing to refresh the page.
Is there a method to replicate this immediate update behaviour via my Workspace Add-On?
A mwe is below.
function onDefaultHomePageOpen() {
// create button
var action = CardService.newAction().setFunctionName('toggleCalVis')
var button = CardService.newTextButton()
.setText("TOGGLE CAL VIS")
.setOnClickAction(action)
.setTextButtonStyle(CardService.TextButtonStyle.FILLED)
var buttonSet = CardService.newButtonSet().addButton(button)
// create CardSection
var section = CardService.newCardSection()
.addWidget(buttonSet)
// create card
var card = CardService.newCardBuilder().addSection(section)
// call CardBuilder.call() and return card
return card.build()
}
function toggleCalVis() {
// fetch calendar with UI name "foo"
var calendarName = "foo"
var calendarsByName = CalendarApp.getCalendarsByName(calendarName)
var namedCalendar = calendarsByName[0]
// Toggle calendar visabilty in the UI
if (namedCalendar.isSelected()) {
namedCalendar.setSelected(false)
}
else {
namedCalendar.setSelected(true)
}
}
In short: Create a chrome extension
(2021-sep-2)Reason: The setSelected() method changes ONLY the data on server. To apply the effect of it, you need to refresh the page. But Google Workspace Extension "for security reason" does not allow GAS to do that. However in an Chrome Extension you can unselect the checkbox of visibility by plain JS. (the class name of the left list is encoded but stable for me.) I have some code for Chrome Extension to select the nodes although I didn't worked it out(see last part).
(2021-jul-25)Worse case: Default calendars won't be selected by getAllCalendars(). I just tried the same thing as you mentioned, and the outcome is worse. I wanted to hide all calendars, and I am still pretty sure the code is correct, since I can see the calendar names in the console.
const allCals = CalendarApp.getAllCalendars()
allCals.forEach(cal => {console.log(`unselected ${cal.setSelected(false).getName()}`)})
Yet, the principle calendar, reminder calendar, and task calendar are not in the console.
And google apps script dev should ask themselves: WHY DO PEOPLE USE Calendar.setSelected()? We don't want to hide the calendar on the next run.
In the official document, none of these two behaviour is mentioned.
TL;DR part (My reason for not using GAS)
GAS(google-apps-script) has less functionality. For what I see, google is trying to build their own eco-system, but everything achievable in GAS is also available via javascript. I can even use typescript and do whatever I want by creating an extension.
GAS is NOT easy to learn. The learning was also painful, I spent 4 hours to build the first sample card, and I can interact correctly with the opened event after 9 hours. The documentation is far from finished.
GAS is poorly supported. The native web-based code editor (https://script.google.com/) is not build for coding real apps, it loses the version control freedom in new interface. And does not support cross-file search. Instead of import, codes run from top to bottom in the list, which you need to find that by yourself. (pass along no extension, no prettier, I can tolerate these)
In comparison with other online JS code editors, like codepen / code sandbox / etcetera it does so less function. Moreover, VSCode also has a online version now(github codespaces).
I hope my 13 hours in GAS are not totally wasted. As least whoever read this can just avoid suffering the same painful test.
Here's the code(typescript) for disable all the checks in Chrome.
TRACKER_CAL_ID_ENCODED is the calendar ID of which I don't want to uncheck. Since it is not the major part of this question, it is not very carefully commented.
(line update: 2022-jan-31) Aware that the mutationsList.length >= 3 is not accurate, I cannot see how mutationsList.length works.
Extension:
getSelectCalendarNode()
.then(unSelectCalendars)
function getSelectCalendarNode() {
return new Promise((resolve) => {
document.onreadystatechange = function () {
if (document.readyState == "complete") {
const leftSidebarNode = document.querySelector(
"div.QQYuzf[jsname=QA0Szd]"
)!;
new MutationObserver((mutationsList, observer) => {
for (const mutation of mutationsList) {
if (mutation.target) {
let _selectCalendarNode = document.querySelector("#dws12b.R16x0");
// customized calendars will start loading on 3th+ step, hence 3, but when will they stop loading? I didn't work this out
if (mutationsList.length >= 3) {
// The current best workaround I saw is setTimeout after loading event... There's no event of loading complete.
setTimeout(() => {
observer.disconnect();
resolve(_selectCalendarNode);
}, 1000);
}
}
}
}).observe(leftSidebarNode, { childList: true, subtree: true });
}
};
});
}
function unSelectCalendars(selectCalendarNode: unknown) {
const selcar = selectCalendarNode as HTMLDivElement;
const calwrappers = selcar.firstChild!.childNodes; // .XXcuqd
for (const calrow of calwrappers) {
const calLabel = calrow.firstChild!.firstChild as HTMLLabelElement;
const calSelectWrap = calLabel.firstChild!;
const calSelcted =
(calSelectWrap.firstChild!.firstChild! as HTMLDivElement).getAttribute(
"aria-checked"
) == "true"
? true
: false;
// const calNameSpan = calSelectWrap.nextSibling!
// .firstChild! as HTMLSpanElement;
// const calName = calNameSpan.innerText;
const encodedCalID = calLabel.getAttribute("data-id")!; // const decodedCalID = atob(encodedCalID);
if ((encodedCalID === TRACKER_CAL_ID_ENCODED) !== calSelcted) {
//XOR
calLabel.click();
}
}
console.log(selectCalendarNode);
return;
}
There is no way to make a webpage refresh with Google Apps Script
Possible workarounds:
From the sidebar, provide users a link that redirects them to the Calendar UI webpage (thus a new, refreshed version of it will be opened)
Install a Goole Chrome extension that refreshes the tab in specified intervals

Quill Editor : Check for change in content in Angular?

I have implemented quill editor in Angular by creating new instance of quill and creating custom toolbar.
this.quill = new Quill('#editor-container', {
modules: {
toolbar: '#toolbar-container'
},
theme: 'snow' // or 'bubble'
});
I have a "update" button which would call the update API. I need to check if the contents of the Quill editor has changed. I am aware of quill.on('text-change'):
this.quill.on('text-change', function(delta, oldDelta, source) {
if (source == 'api') {
console.log('An API call triggered this change.');
} else if (source == 'user') {
console.log('A user action triggered this change.');
}
});
However, I am not sure where do I place this? NgOnInit? NgAfterViewInit? I have created the quill instance in ngAfterViewInit. I know this could be a dumb question.
Any help appreciated! thanks! :)
You can put it in the constructor of the module/component.

chrome.tabs.onUpdated.addListener triggers multiple times

I observe that the onUpdated listener for the tabs API in Chrome does trigger multiple times.
When I refresh the existing tab, the alert pops up 3 times
When I load a different URL, the alert pops up 4 times
In the alert popup, I also see that there seem to be "intermediate" title tags.
How can I avoid this and reduce action to the final update?
chrome.tabs.onUpdated.addListener(function(tabId, changeInfo, tab) {
/*
Multiple Tasks:
1. Check whether title tag matches the CPD Teamcenter title and custom success tab does not exist
2. If yes, trigger three actions:
a. move tab to new Chrome window
b. call external application to hide the window with the isolated tab
c. add custom success tag to identify that this was already processed
*/
const COMPARESTRING = "My Tab Title"
var title = tab.title;
alert(title) // this alert pops up 3 or 5 times!
/* if (title == COMPARESTRING) {
return "Match. :-)";
} else {
return "No match. :-(";
} */
});
you can do something like this
chrome.tabs.onUpdated.addListener(function (tabId, tabInfo, tab): void {
if (tab.url !== undefined && tabInfo.status === "complete") {
// do something - your logic
};
});

Chrome extension webRequest fired multiple times

I have a Chrome extension in which I want to open a dialog box each time the page is partially (ajax) or fully reloaded.
In my background page I am catching the ajax request like this :
chrome.webRequest.onCompleted.addListener(
function(details) {
if (details.frameId == 0) {
chrome.tabs.executeScript(details.tabId, {
"file": "/js/Dialog.js"
});
}
},
{urls: ["https://*/]"}
);
In my Dialog.js I am checking if the dialog box has already been initialized so I do not get multiple dialog boxes but it does not work, it does not seem to be working as I get 2 dialogs. This is what I do to check if it has been initialized :
if (!document.getElementById("my-dialog"))
Events onCompleted fires for each resource was loaded on a tab (images, fonts, styles, etc).
You need to improve you event filtering, or use chrome.tabs.onCreatedand chrome.tabs.onUpdated together in order to catch tab's load finish.
And this:
!document.getElementById("my-dialog")
may not work, because DOM updating is slow operation, so when next event was fired, you DOM may be still not updated.
This simple trick shall work better:
var isLoaded = isLoaded || false;
if(!isLoaded) {
// ... load you dialog ...
isLoaded = true;
}

How to get the active tab in Trigger.io

I'm working on OpenForge browser addon. Is it possible to get the current/active tab object?
The tabbar component doesn't have a button.getActive(success, error) function, so the only way to determine this is to store the active tab in a JavaScript variable when a tab button is tapped.
Example:
forge.tabbar.addButton({
icon: "search.png",
text: "Search",
index: 0
}, function (button) {
// action to perform when button is clicked
button.onPressed.addListener(function () {
alert("Search");
// store active button to variable
active_tab = 'search';
});
});