Facing Issue on Getting Current Tabs and Selected Tab URL on Chrome API - google-chrome

I am trying to get the URL of all opened Tabs and Selected Tab using thiese two snippet
// For Getting URL of All Opened Tabs
chrome.tabs.getCurrent(function(tabs) {
for (var i = 0; i < tabs.length; i++) {
console.log(tab.url);
}
});
// For Getting URL of Selected Tab
chrome.tabs.getSelected(function(tab) {
console.log(tab.url);
});
but neither of them working. For getting All Tabs I am getting this error:
Error in response to tabs.getCurrent: TypeError: Cannot read property 'length' of undefined
and for getting the selected Tab
undefined
can you please let me know why this is happening and how can I fix it?

chrome.tabs.getSelected has been deprecated. so we should use tabs.query({active: true}... instead.
chrome.tabs.getCurrent passes a single tab to the callback function. It doesn't "Getting URL of All Opened Tabs", it:
Gets the tab that this script call is being made from. May be undefined if called from a non-tab context (for example: a background page or popup view).
So:
// Write the URL of the current tab to the console
chrome.tabs.getCurrent(tab => console.log(tab.url));
This requires the "activeTab" or "tabs" permission in the manifest. If there is an error it won't throw an exception, instead it will populate chrome.runtime.lastError.
I find it easier to deal with all the callbacks using an asynchronous or promise wrapper library like chrome-extension-async. This let's us use async/await syntax and a regular try-catch:
try {
const currentTab = await chrome.tabs.getCurrent();
console.log(currentTab.url);
}
catch(err) {
// Handle errors
}
In your popup.html you can't access chrome.tabs.getCurrent - you have to use chrome.tabs.query instead:
async function writeAllTabUrlToConsole() {
try {
// Get all the tabs
const tabs = await chrome.tabs.query({});
// Write all their URLs to the popup's console
for(let t of tabs)
console.log(t.url, t.active);
}
catch(err) {
// Handle errors
}
}

Related

chrome on document load not working, causing carsh

I'm trying to create a Chrome extension, so that I can read it's content when a page is loaded.
document.addEventListener("DOMContentLoaded", function(){
function modifyDOM() {
return "<html>\n".concat(String(document.body.innerHTML),"\n</html>");
}
chrome.tabs.executeScript({
code: '(' + modifyDOM + ')();'
}, (results) => {
let dat=String(results[0]);
console.log(dat);
});
});
But it gives me error saying :
Unchecked runtime.lastError: Cannot access a chrome:// URL
_generated_background_page.html:1 Error handling response: TypeError: Cannot read property '0' of undefined
But the code works fine, when I put the code inside :
chrome.browserAction.onClicked.addListener(function(tab) {
//this works only when I click on that extension icon
...
});
How can I resolve this ?
My code works when I click the button, but I wanted to check for page content change, as I couldn't find any API for that, I tried to do at-least when the page loads.
Since I cannot comment on other posts yet I had to write this in an answer,
check the answer to this question chrome.tabs.executeScript: Cannot access a chrome:// URL
Here's what was said:
chrome.tabs.onUpdated.addListener(function(tabId, changeInfo, tab) {
//code in here will run every time a user goes onto a new tab, so you can insert your scripts into every new tab
});

How to capture $compile or $digest error? (AngularJS directive with templateUrl)

I'm writing a unit test of an AngularJS 1.x directive.
If I use "template" it works.
If I use "templateUrl" it does not work (the directive element remains the same original HTML instead of being "compiled").
This is how I create the directive element to test in Jasmine:
function createDirectiveElement() {
scope = $rootScope.$new();
var elementHtml = '<my-directive>my directive</my-directive>';
var element = $compile(elementHtml)(scope);
scope.$digest();
if (element[0].tagName == "my-directive".toUpperCase()) throw Error("Directive is not compiled");
return element;
};
(this does not actually work, see Update for real code)
I'm using this workaround to use the $httpBackend from ngMockE2E (instead of the one in ngMock). In the browser developer "network" tab I don't see any request to the template file. It seems to work because I solved the error "Object # has no method 'passThrough'".
I know that the call to the template is done asynchronously using the $httpBackend (this means $compile exit before the template is really applied).
My question is:
obviously $compile is not doing what I expect. How can I trap this error?
If I use a wrong address in the templateUrl I don't receive any error.
How can I found the problem happened when I called $compile(directive) or scope.$digest() ?
Thanks,
Alex
[Solution]
As suggested by #Corvusoft I inject $exceptionHandler and I check for errors after every test.
In the end this is the only code I have added:
afterEach(inject(function ($exceptionHandler) {
if ($exceptionHandler.errors.length > 0)
throw $exceptionHandler.errors;
}));
Now I can clearly see the errors occurred in the Jasmine test result (instead of search for them in the console), example:
Error: Unexpected request: GET /api/category/list
No more request expected,Error: Unexpected request: GET /api/category/list
No more request expected thrown
And, most important, my tests does not pass in case there are some errors.
[Update to show real example case]
Actually the real code to make templateUrl work use asynchronous beforeEach ("done") and a timeout to wait the end of compile/digest.
My directive use some prividers/services and the template contains other directives which in turn use their templateUrl and make calls to some APIs in the link function().
This is the current (working) test code:
// workaround to have .passThrough() in $httpBackend
beforeEach(angular.mock.http.init); // set $httpBackend to use the ngMockE2E to have the .passThrough()
afterEach(angular.mock.http.reset); // restore the $httpBackend to use ngMock
beforeEach(inject(function (_$compile_, _$rootScope_, _$http_, $httpBackend, $templateCache, $injector) {
$compile = _$compile_;
$rootScope = _$rootScope_;
$http = _$http_;
$httpBackend.whenGET(/\/Scripts of my app\/Angular\/*/).passThrough();
$httpBackend.whenGET(/\/api\/*/).passThrough(); // comment out this to see the errors in Jasmine
}));
afterEach(inject(function ($exceptionHandler) {
if ($exceptionHandler.errors.length > 0)
throw $exceptionHandler.errors;
}));
beforeEach(function(done) {
createDirectiveElementAsync(function (_element_) {
element = _element_;
scope = element.isolateScope();
done();
});
});
function createDirectiveElementAsync(callback) {
var scope = $rootScope.$new();
var elementHtml = '<my-directive>directive</my-directive>';
var element = $compile(elementHtml)(scope);
scope.$digest();
// I haven't found an "event" to know when the compile/digest end
setTimeout(function () {
if (element.tagName == "my-directive".toUpperCase()) throw Error("Directive is not compiled");
callback(element);
}, 0.05*1000); // HACK: change it accordingly to your system/code
};
it("is compiled", function () {
expect(element).toBeDefined();
expect(element.tagName).not.toEqual("my-directive".toUpperCase());
});
I hope this example helps someone else.
$exceptionHandler
Any uncaught exception in AngularJS expressions is delegated to this
service. The default implementation simply delegates to $log.error
which logs it into the browser console.
In unit tests, if angular-mocks.js is loaded, this service is overridden by mock $exceptionHandler which aids in testing.
angular.
module('exceptionOverwrite', []).
factory('$exceptionHandler', ['$log', 'logErrorsToBackend', function($log, logErrorsToBackend) {
return function myExceptionHandler(exception, cause) {
logErrorsToBackend(exception, cause);
$log.warn(exception, cause);
};
}]);

how to send a Json object to a dialog from the parent using dialog API in Office365

I am new to office 365 word JavaScript API. I am trying to send a Json object to a dialog from the parent using the dialog api. But I couldn't find a better solution for that. I have found it is possible to send a Json object from the dialog to the parent using below code snippet.
Office.context.ui.messageParent
can someone give me a good solution with a code snippet to solve this problem?
You can try something like that
In parent web page (the actual add-in) javascript code
Office.context.ui.displayDialogAsync(url, options, function(result) {
var dialog = result.value;
dialog.addEventHandler(Office.EventType.DialogMessageReceived, function(args){
dialog.close();
var json = JSON.parse(args.message);
//do what ever you need to do...
});
});
NOTE: for the sake of simplicity I omitted "error checks" if callback function receive error result. You should take care of that as well.
The web page that is opened at url will have a function for pushing back the json object after representing it as a string
var asString = JSON.stringify(myObj);
Office.context.ui.messageParent(asString);
Of course the webpage opened in the dialog window must also reference Office.js.
Here is the documentation link for this so-called dialogAPI https://dev.office.com/reference/add-ins/shared/officeui
Edit:
the original question is to send data from parent to children
If you need to send info to the page opened in dialogAPI. I suggest your append query parameters to url. You can stringify your Json object and pass it. This is not very clean thought.
Standardized way to serialize JSON to query string?
You can send JSON data or object back to your parent easily.
This code snippet should be in your child page's(Dialog page) JS file.
(function () {
"use strict";
// The Office initialize function must be run each time a new page is loaded
Office.initialize = function (reason) {
$(document).ready(function () {
$('#btnLogin').click(submit);
});
};
function submit() {
// Get and create the data object.
var email = $('#txtEmail').val();
var password = $('#txtPassword').val();
var data = {
email: email,
password: password
}
// Create the JSON and send it to the parent.
var json = JSON.stringify(data);
Office.context.ui.messageParent("json");
}
})();
See here: https://dev.office.com/docs/add-ins/develop/dialog-api-in-office-add-ins
Find section "Passing information to the dialog box".
Two primary ways:
Add query parameters to the URL
Store the information somewhere that is accessible to both the host window and dialog box, e.g. local storage

How do you use getHtmlPrintDocumentSourceAsync to print in HTML/JavaScript Windows Store apps?

I am very simply trying to print some content in a Windows 10 app (Universal) using HTML and JavaScript/WinJS.
ALL of the documentation says that there is a function on MSApp called getHtmlPrintDocumentSource.
I do not have this, nor can I seem to find any relevant source to see if it may have been moved. I instead have getHtmlPrintDocumentSourceAsync. This seems to be a replacement for the former, but I cannot get it to work and there is zero documentation on it as far as I can tell.
When I run the below code (which is based on the documentation but updated to be async):
function onPrintTaskRequested(printEvent) {
var printTask = printEvent.request.createPrintTask("Print Sample", function (args) {
MSApp.getHtmlPrintDocumentSourceAsync(document)
.then(function(result) {
args.setSource(result);
});
printTask.oncompleted = onPrintTaskCompleted;
});
}
result is populated with some of the print settings as I would expect, but the content property is set to 0, which I am guessing is the problem. I can't really be sure as there is no documentation for this function. I can't even run any of the dozens of pieces of example code in the documentation using `getHtmlPrintDocumentSource' because it seemingly doesn't exist anymore.
In addition to just sending document to the Async method, I have tried a couple of different variations of creating document fragments. Same results.
Probably not terribly helpful, but the message in the Windows Print Dialog that opens when executing the above code is: "Nothing was sent to print. Open a document and print again."
Any ideas?
getHtmlPrintDocumentSource is a synchronous deprecated API in Windows 10 apps. We'll work on some of the docs left behind for Windows 8 and 8.1 to clarify that.
Check out https://github.com/Microsoft/Windows-universal-samples/tree/master/Samples/Printing/js for an example of how to use getHtmlPrintDocumentSourceAsync in JavaScript.
Here is the code:
// Needs to be invoked before calling the print API
function registerForPrintContract() {
var printManager = Windows.Graphics.Printing.PrintManager.getForCurrentView();
printManager.onprinttaskrequested = onPrintTaskRequested;
console.log("Print Contract registered. Use the Print button to print.", "sample", "status");
}
// Variable to hold the document source to print
var gHtmlPrintDocumentSource = null;
// Print event handler for printing via the PrintManager API.
function onPrintTaskRequested(printEvent) {
var printTask = printEvent.request.createPrintTask("Print Sample", function (args) {
args.setSource(gHtmlPrintDocumentSource);
// Register the handler for print task completion event
printTask.oncompleted = onPrintTaskCompleted;
});
}
// Print Task event handler is invoked when the print job is completed.
function onPrintTaskCompleted(printTaskCompletionEvent) {
// Notify the user about the failure
if (printTaskCompletionEvent.completion === Windows.Graphics.Printing.PrintTaskCompletion.failed) {
console.log("Failed to print.", "sample", "error");
}
}
// Executed just before printing.
var beforePrint = function () {
// Replace with code to be executed just before printing the current document:
};
// Executed immediately after printing.
var afterPrint = function () {
// Replace with code to be executed immediately after printing the current document:
};
function printButtonHandler() {
// Optionally, functions to be executed immediately before and after printing can be configured as following:
window.document.body.onbeforeprint = beforePrint;
window.document.body.onafterprint = afterPrint;
// Get document source to print
MSApp.getHtmlPrintDocumentSourceAsync(document).then(function (htmlPrintDocumentSource) {
gHtmlPrintDocumentSource = htmlPrintDocumentSource;
// If the print contract is registered, the print experience is invoked.
Windows.Graphics.Printing.PrintManager.showPrintUIAsync();
});
}

Chrome Devpanel Extension Communicating with Background Page

I have an extension to the chrome devtools panel. I can send messages to the page using chrome.devtools.inspectedWindow.eval ... but how do I receive messages in the dev panel? Specifically, I need my devpanel to hook into events that happen on the page. I can't get it to listen to events on my content script, nor the background page.
I've tried chrome.extension.sendMessage in the content script, along with chrome.extension.onMessage.addListener in the dev panel script. But sendMessage complains with Port error: Could not establish connection. Receiving end does not exist.
The issue persists with long-lived connections:
In content script or background page:
var port = chrome.extension.connect({name: "test"});
port.postMessage({msg: "testing"});
In dev tools panel javascript:
chrome.extension.onConnect.addListener(function(port) {
port.onMessage.addListener(function(msg) {
// never gets here
});
});
How can I listen for events that are triggered in my content script-- in my dev tool panel? A diagram like this from Firefox's Add-On SDK would be great: https://addons.mozilla.org/en-US/developers/docs/sdk/latest/static-files/media/content-scripting-overview.png
The goal is to create a channel ("port") for communication. It does not matter how the port is created, as long as the connection is correctly maintained.
The devtools script has to initiate the port, because the background script does not know when a devtools panel is created.
Here's a basic example, which shows a bidirectional communication method:
devtools.js
chrome.devtools.panels.create('Test', '/icon.png', '/panel.html', function(extensionPanel) {
var _window; // Going to hold the reference to panel.html's `window`
var data = [];
var port = chrome.runtime.connect({name: 'devtools'});
port.onMessage.addListener(function(msg) {
// Write information to the panel, if exists.
// If we don't have a panel reference (yet), queue the data.
if (_window) {
_window.do_something(msg);
} else {
data.push(msg);
}
});
extensionPanel.onShown.addListener(function tmp(panelWindow) {
extensionPanel.onShown.removeListener(tmp); // Run once only
_window = panelWindow;
// Release queued data
var msg;
while (msg = data.shift())
_window.do_something(msg);
// Just to show that it's easy to talk to pass a message back:
_window.respond = function(msg) {
port.postMessage(msg);
};
});
});
Now, the panel is capable of sending/receiving messages over a port. The panel's script (external script file, because of the CSP) may look like:
panel.js
function do_something(msg) {
document.body.textContent += '\n' + msg; // Stupid example, PoC
}
document.documentElement.onclick = function() {
// No need to check for the existence of `respond`, because
// the panel can only be clicked when it's visible...
respond('Another stupid example!');
};
Now, the background page's script:
background.js
var ports = [];
chrome.runtime.onConnect.addListener(function(port) {
if (port.name !== "devtools") return;
ports.push(port);
// Remove port when destroyed (eg when devtools instance is closed)
port.onDisconnect.addListener(function() {
var i = ports.indexOf(port);
if (i !== -1) ports.splice(i, 1);
});
port.onMessage.addListener(function(msg) {
// Received message from devtools. Do something:
console.log('Received message from devtools page', msg);
});
});
// Function to send a message to all devtools.html views:
function notifyDevtools(msg) {
ports.forEach(function(port) {
port.postMessage(msg);
});
}
To test, simply run notifyDevtools('Foo'); on the background page (e.g. via the console). In this demo, the message will be sent to all devtools. Upon receipt, the devtools panel will contain the received message.
Put the extension together using:
manifest.json
{
"name": "Test",
"manifest_version": 2,
"version": "1",
"devtools_page": "devtools.html",
"background":{"scripts":["background.js"]}
}
panel.html
<script src="panel.js"></script> <!-- Doctype etc not added for conciseness-->
devtools.html
<script src="devtools.js"></script>
See also
How to modify content under a devtools panel in a Chrome extension?
chrome.devtools API
Message passing: Long-lived connections
Content Security Policy in Chrome extensions ("Inline JavaScript (...) will not be executed. This restriction bans both inline <script> blocks and inline event handlers.")