It seems it's fairly common practice to grab the contents of a Google-calendar embed code, and add a stylesheet into it (either manually or through something like a PHP script) and display a custom-styled public calendar.
The odd thing is, I noticed if you click the print button at the top, or the "Google Calendar" in the lower right, it goes to localhost or whatever domain the page is - not the Google calendar.
If you try to trace the "gcal$func$[3]();" onclick through the Chrome devtools, or through Firefox with gcal$func$[3].toSource(); it will not find it or say
"function () {
[native code]
}"
So where is this function coming from, and how can you tweak this to make it open in a new window with the Google url, not the current domain (404)?
According to the Google Calendar embed JS code, the function points to the following code
window.open(Pf(this.c.i.Nb + "/render", "cid", b))
this.c.i.Nb represents the base URL, which is by default the domain where the script runs (in your case that's your domain). However, it's intended to be google.com domain and fortunately, it's very easy to change that. baseURL is one of the parameters in the initialization script (declared in the page you're grabbing) and you just need to configure that to https://www.google.com.
If you use PHP, your code might look like this.
$page = new DOMDocument("1.0", "utf-8");
// grab the Google Calendar code
$page->loadHTMLfile("https://www.google.com/calendar/embed?src=yourcalendar%40gmail.com");
// set up the baseUrl and print the grabbed page
echo str_replace('"baseUrl":"/"', '"baseUrl":"https://www.google.com/"', $page->saveHTML());
Now all the links should work correctly.
Related
Background
To pass a client_id from one domain to another, Google supports adding a "linker" parameter to outgoing Links that are part of the cross-domain tracking setup. This linker parameter contains the client_id, session_id (I believe, information about Google Ads, e.g. gclid) and a basic fingerprint + timestamp. On the receiving domain, if the browser fingerprint matches and the timestamp is not too far in the past, the passed client_id and session_id are stored in a first party cookie on the 2nd domain and consequently used.
analytics.js / GA-UA
With analytics.js (GA-UA) you could easily do the following, to decorate URLs manually:
function decorateUrl(urlString) {
var ga = window[window['GoogleAnalyticsObject']];
var tracker;
if (ga && typeof ga.getAll === 'function') {
tracker = ga.getAll()[0]; // Uses the first tracker created on the page
urlString = (new window.gaplugins.Linker(tracker)).decorate(urlString);
}
return urlString;
}
Yet, when only gtag is loaded, window.ga and window.gaplugins are not defined. As far as I see, there is currently no documented way to manually generate links with the linker parameter with gtag.
In Google's documentation, they suggest setting up the linker manually. (https://support.google.com/analytics/answer/10071811?hl=en#zippy=%2Cmanual-setup)
But this has several disadvantages, e.g. I have to create a custom "fingerprint" logic (so that decorated URLs are not shared) and e.g. Google Ads information is not included.
Either way, I would like to use the internal gtag logic to decorate URLs.
"Hacky" Workaround Solution
gtag automatically decorates a tags (as soon as they're clicked) that lead to a cross-domain-tracking domain specified in the GA4 data stream settings (e.g. "test.com"), but I specifically need to decorate URLs manually (i.e. without immediately redirecting to them).
I thought about doing the following:
Create a dummy, hidden a tag with the URL to decorate
Prevent redirection with onclick='event.preventDefault();'
Simulate click on hidden element so that gtag automatically adds the linker url parameter to the href attribute
Extract new href attribute
Remove hidden element
function decorateUrlGtag(urlString) {
var tempAnchorEl = document.createElement("a");
tempAnchorEl.setAttribute("type", "hidden");
tempAnchorEl.setAttribute("href", urlString);
tempAnchorEl.setAttribute("onclick", "event.preventDefault(); return false");
document.body.appendChild(tempAnchorEl);
tempAnchorEl.click();
var urlWithLinker = tempAnchorEl.href;
tempAnchorEl.remove();
return urlWithLinker;
}
This also does not work, because gtag does not seem to register the tempAnchorEl.click(); call. If I click the link manually, the URL is decorated - as expected.
Suggested Solutions
The solutions outlined here (Google Analytics gtag.js Manually adding the linker cross-domain parameter to URLs) also do not work for me:
Answer: Even after gtag is initiated, I do not see a global ga element
Answer: Same problem (no ga defined)
Do you (1) know if there is a way to generate the linker parameter manually with gtag that I have overlooked, (2) know how to make my "hacky" solution work or (3) have another possible solution?
I haven't grokked this solution, and I am not sure it answers your question directly, but Simo does give an outline of how to configure GA4 cross domain tracking here:
https://www.simoahava.com/gtm-tips/cross-domain-tracking-google-analytics-4/#how-to-configure-cross-domain-tracking-manually
He breaks the problem down into steps but does not go into great detail. He provides one code snippet:
"...you could also load the URL parameter values directly into the GA4 configuration with something like:
gtag('config', 'G-12345', {
// Namespace roll-up trackers
cookie_prefix: 'roll-up',
// Pull in the Client ID from the URL
client_id: (new URLSearchParams(document.location.search)).get('client_id'),
// Pull in the Session ID from the URL
session_id: (new URLSearchParams(document.location.search)).get('session_id')
});
"
Hope that helps!
Follow the following steps.
Steps 1:
Go to google.
Open the javascript console.
Enter the command: document.all.q.value = "hello"
As expected the element with a name of "q" (the search field) is set to "hello").
Steps 2:
Go to google.
In the address bar type javascript: document.all.q.value = "hello!"
Press Enter
If your browser is either Internet Explorer, or Google Chrome, the javascript will have replaced the google website with an entirely blank page, with the exception of the word "Hello".
Finally
Now that you've bugged out your browser, go back to Google.com and repeat Steps 1. You should receive an error message "Uncaught ReferenceError: document is not defined (...) VM83:1
Question:
Am I doing something wrong? And is there another method which works, while still using the address bar for JS input?
The purpose of a javascript: scheme URL is to generate a new page using JavaScript. Modifying the existing page with it is something of a hack.
document.all.q.value = "hello!"; evalues as "hello!", so when you visit that URL, a new HTML document consisting solely of the text hello! is generated and loaded in place of the existing page.
To avoid this: Make sure the JS does not return a string. You can do this by using void.
javascript:void(document.all.q.value = "hello!");
When messing around with javascript: in the adressbar some (if not the most) browsers handle it as a new page, so you have to add a window.history.back(); at the end
javascript: document.all.q.value = "hello!"; window.history.back();
How can I communicate from a JavaScript code of a webpage to the main code of the add-on?
For example, something like this: If some element is clicked, in the corresponding event handler of the page script, which is the syntax that can be used to send some message to the main code?
Specifically, something like this, where the frame now must be replaced by a generic webpage. Is it possible?
Edit: I have tried the suggested code, but how I had said, the application returns this error:
console.error: sherlock:
Message: ReferenceError: document is not defined
Stack:
A coding exception was thrown in a Promise resolution callback.
See https://developer.mozilla.org/Mozilla/JavaScript_code_modules/Promise.jsm/Promise
Full message: ReferenceError: document is not defined
Previously my question, I had infact tried something similar without any effect.
Yes it is possible.
document.onload = function() {
var elementYouWant = document.getElementById("someID");
elementYouWant.onclick = console.log("Yup.. It was clicked..");
};
Reference.
The answer to the question is not as trivial as it may seem at first sight. I had also thought of a logic of the type described in the Pogrindis' response.
But here, in the case of interaction between the main script (i.e. that of the add-on) and generic script of arbitrary documents, the pattern is different.
In summary, the interaction takes place in this way:
It is required the API page-mod.
Through the property includes of the object PageMod you create a reference to the document, specifying the URI (wildcards are allowed).
Via the contentScriptFile property it is set the URL of the .js file that will act as a vehicle between the main code and that of the document.
Here's an example that refers to the specific needs of the context in which I am. We have:
an add-on code (the main code);
a Sidebar type html document (gui1.html) loaded in the file that I
use as a simple UI (I advise against the use of Frames, since it does
not support many typical HTML features - eg the click on a link,
etc.) containing a link to a second document (gui2.html) which will then
be loaded into the browser tab (I needed this trick because the
Sidebar does not support localStorage, while it is necessary for me);
a script in the document.
We must create an exchange of information between the two elements. In my case the exchange is unidirectional, from the page script to the main one.
Here's the code (main.js):
var pageMod = require("sdk/page-mod");
pageMod.PageMod({
include: "resource://path/to/document/gui2.html",
contentScriptFile: data.url("listen.js"),
onAttach: function(worker) {
worker.port.on("gotElement", function(elementContent) {
console.log(elementContent);
});
}
});
and in the html page script:
<script type="text/javascript">
[...]
SOWIN = (navigator.userAgent.toLowerCase().indexOf("win") > -1) ? "win" : "nix";
if (SOWIN == "win") {
window.postMessage("win","*");
} else {
window.postMessage("Linux","*");
}
[...]
</script>
Finally in the JS file (listen.js) to be attached to the page script:
window.addEventListener('message', function(event) {
self.port.emit("gotElement", event.data);
}, false);
This is just a small example, but logic I would say that it is clear. The uploaded content scripts are not accessible directly from main.js (i.e. the add-on), but you can create a bidirectional communication through the exchange of messages. To achieve this we have to put ourselves in listening the event Attach of the page-mod. Then, it is passed a worker object to the listener; that worker may be used by the add-on for the exchange of messages.
Here are the references to have an exhaustive picture:
Interacting with page scripts
Communicating with other scripts
page-mod
port
Communicating using "port"
postMessage
Communicating using postMessage
I've got a web page with its own scripts and variables that I need to execute and retrieve return values from my extension's Background.js.
I understand (I think!) that in order to interact with the web page, it must be done via chrome.tabs.executeScript or a ContentScript, but because the code must execute in the context of the original page (in order to have scope to the scripts and variables), it needs to be injected into the page first.
Following this great post by Rob W, I'm able to invoke the page-level script/variables, but I'm struggling to understand how to return values in this way.
Here's what I've got so far...
Web page code (that I want to interact with):
<html>
<head>
<script>
var favColor = "Blue";
function getURL() {
return window.location.href;
}
</script>
</head>
<body>
<p>Example web page with script content I want interact with...</p>
</body>
</html>
manifest.json:
{
// Extension ID: behakphdmjpjhhbilolgcfgpnpcoamaa
"name": "MyExtension",
"version": "1.0",
"manifest_version": 2,
"description": "My Desc Here",
"background": {
"scripts": ["background.js"]
},
"icons": {
"128": "icon-128px.png"
},
"permissions": [
"background",
"tabs",
"http://*/",
"https://*/",
"file://*/", //### (DEBUG ONLY)
"nativeMessaging"
]
}
background.js
codeToExec = ['var actualCode = "alert(favColor)";',
'var script = document.createElement("script");',
' script.textContent = actualCode;',
'(document.head||document.documentElement).appendChild(script);',
'script.parentNode.removeChild(script);'].join('\n');
chrome.tabs.executeScript( tab.id, {code:codeToExec}, function(result) {
console.log('Result = ' + result);
} );
I realise the code is currently just "alerting" the favColor variable (this was just a test to make sure I could see it working). However, if I ever try returning that variable (either by leaving it as the last statement or by saying "return favColor"), the executeScript callback never has the value.
So, there appear to be (at least) three levels here:
background.js
content scripts
actual web page (containing scripts/variables)
...and I would like to know what is the recommended way to talk from level 1 to level 3 (above) and return values?
Thanks in advance :o)
You are quite right in understanding the 3-layer context separation.
A background page is a separate page and therefore doesn't share JS or DOM with visible pages.
Content scripts are isolated from the webpage's JS context, but share DOM.
You can inject code into the page's context using the shared DOM. It has access to the JS context, but not to Chrome APIs.
To communicate, those layers use different methods:
Background <-> Content talk through Chrome API.
The most primitive is the callback of executeScript, but it's impractical for anything but one-liners.
The common way is to use Messaging.
Uncommon, but it's possible to communicate using chrome.storage and its onChanged event.
Page <-> Extension cannot use the same techniques.
Since injected page-context scripts do not technically differ from page's own scripts, you're looking for methods for a webpage to talk to an extension. There are 2 methods available:
While pages have very, very limited access to chrome.* APIs, they can nevertheless use Messaging to contact the extension. This is achieved through "externally_connectable" method.
I have recently described it in detail this answer. In short, if your extension declared that a domain is allowed to communicate with it, and the domain knows the extension's ID, it can send an external message to the extension.
The upside is directly talking to the extension, but the downside is the requirement to specifically whitelist domains you're using this from, and you need to keep track of your extension ID (but since you're injecting the code, you can supply the code with the ID). If you need to use it on any domain, this is unsuitable.
Another solution is to use DOM Events. Since the DOM is shared between the content script and the page script, an event generated by one will be visible to another.
The documentation demonstrates how to use window.postMessage for this effect; using Custom Events is conceptually more clear.
Again, I answered about this before.
The downside of this method is the requirement for a content script to act as a proxy. Something along these lines must be present in the content script:
window.addEventListener("PassToBackground", function(evt) {
chrome.runtime.sendMessage(evt.detail);
}, false);
while the background script processes this with a chrome.runtime.onMessage listener.
I encourage you to write a separate content script and invoke executeScript with a file attribute instead of code, and not rely on its callback. Messaging is cleaner and allows to return data to background script more than once.
The approach in Xan's answer (using events for communication) is the recommended approach. Implementing the concept (and in a secure way!) is however more difficult.
So I'll point out that it is possible to synchronously return a value from the page to the content script. When a <script> tag with an inline script is inserted in the page, the script is immediately and synchronously executed (before the .appendChild(script) method returns).
You can take advantage of this behavior by using the injected script to assign the result to a DOM object which can be accessed by the content script. For example, by overwriting the text content of the currently active <script> tag. The code in a <script> tag is executed only once, so you can assign any rubbish to the content of the <script> tag, because it won't be parsed as code any more. For example:
// background script
// The next code will run as a content script (via chrome.tabs.executeScript)
var codeToExec = [
// actualCode will run in the page's context
'var actualCode = "document.currentScript.textContent = favColor;";',
'var script = document.createElement("script");',
'script.textContent = actualCode;',
'(document.head||document.documentElement).appendChild(script);',
'script.remove();',
'script.textContent;'
].join('\n');
chrome.tabs.executeScript(tab.id, {
code: codeToExec
}, function(result) {
// NOTE: result is an array of results. It is usually an array with size 1,
// unless an error occurs (e.g. no permission to access page), or
// when you're executing in multiple frames (via allFrames:true).
console.log('Result = ' + result[0]);
});
This example is usable, but not perfect. Before you use this in your code, make sure that you implement proper error handling. Currently, when favColor is not defined, the script throws an error. Consequently the script text is not updated and the returned value is incorrect. After implementing proper error handling, this example will be quite solid.
And the example is barely readable because the script is constructed from a string. If the logic is quite big, but the content script in a separate file and use chrome.tabs.executeScript(tab.id, {file: ...}, ...);.
When actualCode becomes longer than a few lines, I suggest to wrap the code in a function literal and concatenate it with '(' and ')(); to allow you to more easily write code without having to add quotes and backslashes in actualCode (basically "Method 2b" of the answer that you've cited in the question).
chrome.browserAction.onClicked.addListener(function(tab) {
// No tabs or host permissions needed!
console.log('Turning ' + tab.url + ' red!');
chrome.tabs.executeScript({
file: 'index.js'
});
});
Here index.js is normal js file to inject in browser
#index.js
alert("Hello from api");
I am coding a small app ,in middle i struck at some point where i have to execute javascript to get my data ?
in my process ,i have to login to some url and then go to some page and have to get data from that . i done all this with indy idhttp ,i got all info except one column which needs javascript to get value ,then i tried using twebbowser to make it work for me ,but how can i use cookies to enabled to webbrowser ?
i navigated browserto('http://mysite.com/login.php user and pass ') ,well its loged in and then i tried to access next link like ('http://mysite.com/link1/example.php')but it directing to login page :(
any help appreciated :)
What is your question now? In the title you ask how to execute JavaScript. Try the following:
uses
MSHTML_TLB, SHDocVw, ShellAPI;
function ExecuteScript(doc: IHTMLDocument2; script: string; language: string): Boolean;
var
win: IHTMLWindow2;
Olelanguage: Olevariant;
begin
if doc <> nil then
begin
try
win := doc.parentWindow;
if win <> nil then
begin
try
Olelanguage := language;
win.ExecScript(script, Olelanguage);
finally
win := nil;
end;
end;
finally
doc := nil;
end;
end;
end;
Sample usage:
IDoc: IHTMLDocument2;
Webbrowser1.Document.QueryInterface(IHTMLDocument2, iDoc);
ExecuteScript(iDoc, 'document.login.submit()', 'JavaScript');
(This, and more, can be found here).
Then in the text you ask how to use cookies (when using TWebBrowser this should happen automatically). When using Indy HTTP, you just have to attach a TIdCookieManager to your TIdHTTPClient instance, thats all (but you probably don't want to use that anyway, due to the script requirement....)
Your best bet would be to automate IE itself. Grab a copy of embeddedwb, drop on a form and navigate to the url which you need to execute something. There is a document property of the component which returns an OLEVariant, use this to execute a DHTML style statement.. something like document.form.submit;.
You can easily hide the window used for automation, one technique I have used is to place it on a new page on a page control, add a second page to display the status, then show the status page and hide the tabs.