EditContext is null — Page Properties dialog will not open - aem

Using the standard init.jsp from /libs/foundation/components/page/, there is a line in the head of my page to provide the dialog path:
if (editContext != null && editContext.getComponent() != null) {
dlgPath = editContext.getComponent().getDialogPath();
}
editContext here is returning null, which means the dialog won't load for the page itself. Components on the page are unaffected & from the context menu on the site admin, the page dialog will also load without issue.
Above is using the standard cq:defineObjects tag to initialise editContext, though it will also return null via WCMUtils.getComponentContext(request).getEditContext()
The ComponentContext returns fine. If I adapt init.jsp to use componentContext.getComponent().getDialogPath(), the dialog will load successfully, but I want to avoid tampering with init.jsp.
I know this is an issue with my own bespoke code (Geometrixx is unaffected), but I'm not sure how to debug why the EditContext is failing to load.

Related

Detecting if browser extension popup is running on a tab that has content script

There a few similar questions, but none of them have really gotten at what I'm asking.
I have a browser action popup. In the popup, I want to display settings if you're on a page where the content script has been injected (i.e., any page that matches the matches key within the content_scripts in the `manifest).
If I'm on a page that doesn't match the content_scripts matches pattern (and so wasn't injected), I just want to display a generic message "this plugin activates when you're on so-and-so sites".
What is the cleanest way to do it, without adding any unnecessary permissions?
It seems like one option is sending a message to a content script in the active tab, and seeing if I get a reply, but that seems really.. hacky. I should be able to know just based on a regex if I'm on one of the domains that matches my content script.
I'm looking for something that works in both manifest v2 and v3, btw.
TL;DR;
What's the simplest way to display a "you're on a page that matches your content_script" or "you're not on a page that matches your content_script" in a browser_action popup?
I build chrome extensions full time for an agency and have had projects where I needed to do exactly what you're asking.
The solution can be implemented w/o any permissions whatsoever. I built mine locally with an empty array for permissions. (for mv3)
for popup.html just create 2 divs and have them default to display none.
<div id="unsupported" style="display: none;">Ooops! This is not a supported site.</div>
<div id="supported" style="display: none;">Wohoo! This is a supported site!!!!!</div>
for your script.js, wait till the popup loads then query the active tab in the current window and get that tab's ID to send a message directly to it. If the tab is supported with a content script, it will send a true response (see last code snippet). If it wasn't supported, it will be an 'undefined' response.
async function setUI() {
let tabData = await chrome.tabs.query({ active: true, currentWindow: true })
let tabId = tabData[0].id // tabs.query returns an array, but we filtered to active tab within current window which yields only 1 object in the array
chrome.tabs.sendMessage(tabId, {
'message': 'isSupported'
}, (response) => {
console.log(response)
// response will be true if the message was successfuly sent to the tab and "undefined" if the message was never received (i.e. not supported w/ your content script)
if (response) return showSupportedHTML()
// else
showUnsupportedHTML()
})
}
function showSupportedHTML() {
document.querySelector('#supported').style['display'] = ''
}
function showUnsupportedHTML() {
document.querySelector('#unsupported').style['display'] = ''
}
window.addEventListener('DOMContentLoaded', () => {
setUI()
})
Lastly, in your content script, add a message listener to receive the message 'isSupported' that comes in from your content script. If the content script receives that message, have it send a response back with 'true'.
chrome.runtime.onMessage.addListener(function (request, sender, sendResponse) {
if (request.message == 'isSupported') {
console.log('run')
sendResponse(true)
}
})
Now, this of course only works for manifest v3 because as far as I know you can't use chrome.tabs.query for mv2. However, I recommend this solution as I've implemented pretty much this exact same code in other projects for clients and it's never had any issues.
I could look into a solution for mv2, though using the "activeTab" permission would be the right way to do it, I believe. Now, if you really don't want to go that route then you could implement a rather hacky solution. For example, you could use window 'focus' and window 'blur' events to see when a user has entered or left a tab. Then set a local storage variable every time a user enters / leaves a supported page. The order of operations for blur and focus is always blur => focus. So, when the blur event occurs you set a local storage variable to false. However, if you leave a supported tab for another supported tab then the 'focus' event will trigger immediately afterwards so you can set that same storage variable back to true.
Now, your content script will load after the tab has been focused so you'll need to add a function for when the page loads. You can run something like document.hidden and if that returns true, do nothing because the user already left this tab. If it returns false, then the user is still on the tab and you can set your local storage variable to true.
When the user opens the popup, you'll check that local storage variable and if its true or false, you can set the UI accordingly.
Let me know if the mv2 solution made sense or sounds too hacky. Happy to look into it more! :)
edit: Here is the code for mv2, I tested it and it does work and without any permissions, other than storage which is not an invasive permission.
Script.js for the mv2 popup:
async function setUI() {
chrome.storage.local.get(['isSupported'], function (response) {
console.log(response['isSupported'])
// response will be true if the message was successfuly sent to the tab and "undefined" if the message was never received (i.e. not supported w/ your content script)
if (response['isSupported']) return showSupportedHTML()
// else
showUnsupportedHTML()
})
}
function showSupportedHTML() {
document.querySelector('#supported').style['display'] = ''
}
function showUnsupportedHTML() {
document.querySelector('#unsupported').style['display'] = ''
}
window.addEventListener('DOMContentLoaded', () => {
setUI()
})
code for the content script in mv2:
if (!document.hidden) chrome.storage.local.set({'isSupported': true})
window.addEventListener('blur', () => {
console.log('left site')
chrome.storage.local.set({'isSupported': false})
})
window.addEventListener('focus', () => {
console.log('entered site')
chrome.storage.local.set({'isSupported': true})
})
Let me know if you have any additional questions.
Disclaimer: I have no prior browser extension development experience and am just going by the docs. I might be spouting nonsense or giving an answer that is plainly against your requirements, but that would be out of ignorance and not malicious intent. If you find my answer problematic, comment, or cast a vote and move on.
According to MDN, the activeTab permission allows to read the active tab's Tab.url property. One solution could be to request that permission, and then use that API to get the active tab's URL, and then use the same regex from the manifest.json's matches property to test for a match, and then use that information to modify your extension's browser_action UI.
You should be able to read the matches property from the manifest file via the .runtime.getManifest() API. MDN docs, chrome docs.
Snippet to get active tab in a background script: tabs.query({active: true}). (link to MDN docs). A content script should instead use tabs.getCurrent and the Tab.active property of the resolved result.
If you don't want to request the activeTab permission, what you're suggesting with the message-passing between the browser_action scripts and the content scripts might be the right way to go, but I don't know for a fact. The tabs.onActivated event would probably be useful with this approach. Note that to send a message from a background script to a content script, you need to use tabs.sendMessage (MDN docs, chrome docs) instead of runtime.sendMessage.
Another possible (maybe?) approach would be to listen for the tab change in the content script and then send the notification message from the content script to the extension's background scripts via the onfocus event (or similar events), and runtime.sendMessage.
If you go with a messaging-related approach, you might want to put a condition in the content script to only do messaging if the content script is in the top frame of the tab (Ie. iframes don't do messaging), since only one frame of the tab really needs to do this kind of messaging when the active tab changes, and content scripts can be applied to all frames in a browsing context.
Of these possible solutions I can think of, I don't know which is best for you, since you want both minimal permission requirements and a simple/clean approach, and each seems to be a tradeoff.

Fiori -- Cross application navigation; Handling startup parameters?

This question is related to:
Fiori - Cross Application Navigation
http://help.sap.com/saphelp_uiaddon10/helpdata/en/07/9561b716bb4f2f8ae4e47bacbdb86d/content.htm
Remove URL params on routing
My use case is like this:
I have multiple applications that should link to others (deep).
Since documentation of cross navigation mention to avoid deep links I decided to use a startup parameter.
For example:
Application A has a list of some items in the detail view of one item there is a reference to another application B that contains some other details.
Assume A shows article details and B shows some details of a producer of an article.
Application A would now use some navigation like this:
sap.ushell.Container.getService("CrossApplicationNavigation").hrefForExternal({
target : { semanticObject : "ApplicationB", action : "display" },
params : { "someID" : "102343333"}
})
Now in application B I use code like this inside the Component.js at the end of the init method.
var oRouter = that.getRouter().initialize();
var oComponentData = this.getComponentData();
if (oComponentData.startupParameters) {
oRouter.navTo("SomeView", {
someId : oComponentData.startupParameters.someID[0],
}, false);
}
First question: Is this the right place for handling the startup parameters?
Second question: If I using the navigation the startup parameter will still be in the code, I would prefer to remove it, but how?
Update
In the target application (B) it would lead to the following URL:
https://server/sap/bc/ui5_ui5/ui2/ushell/shells/abap/FioriLaunchpad.html?sap-client=100&sap-language=EN#SemObject-display?someID=102343333&/SomeView(102343333)/
Anyhow I would prefere to have something like this:
https://server/sap/bc/ui5_ui5/ui2/ushell/shells/abap/FioriLaunchpad.html?sap-client=100&sap-language=EN#SemObject-display?/SomeView(102343333)/
The parameter must be retrieved as
var oComponentData = this.getComponentData();
if (oComponentData.startupParameters) {
oRouter.navTo("SomeView", {
someId : oComponentData.startupParameters.someID[0],
}, false);
as you write. In Fiori applications, the startup parameters injected into the Component data of your constructor may have been renamed, enriched by further default values etc.. Thus they may be distinct from the parameter one observes in the url. Applications are advised to refrain from trying to inspect the URL directly.
If one supplies a very long set of url parameters, one will observer that the FLP replaces some of them with sap-intent-param=AS123424 ("compacted URL") to work around url length restrictions on some platforms and in bookmarks, in the
getComponentData().startupParameters one will receive the full set of parameters).
As to the second question.
No, there is currently no way to "cleanse" the URL and avoid the redundancy between and inner app route.
SemObject-display?someID=102343333&/SomeView(102343333)/
which after navigation may look like
SemObject-display?someID=102343333&/SomeView(102343999)/
App was started with 102343333, but then user navigated within the app to another item (102343999).
Any change in the "Shell-part" of the has (SemObject-display?someID102343333) will lead to a cross-app-navigation (reinstantiation of your component) with a different startupParameter.
(There are cases where this is desired in the flow, e.g. a cross navigation from a OrgUnit factsheet to the parent OrgUnit factsheet via a link).
There were ideas within SAP to fuse the inner-app routes and the intent parameters, but they were not carried out, as it's mostly url aesthetics.
Note: To support boomarking, one has to respect both startup parameters and
inner app route during component instantiation,
assuming the user created a bookmark on
SemObject-display?someID=102343333&/SomeView(102343999)/
(While he was looking at 9999(!)).
When reinstantiating the app, the inner app route should take higher precedence than startup-parameters.
So amend the code to:
var oComponentData = this.getComponentData();
if (oComponentData.startupParameters) {
if (sap.ui.core.getHashChanger().getHash()=== "") {
// if no inner app route present, navigate
oRouter.navTo("SomeView", {
someId : oComponentData.startupParameters.someID[0],
}, false);
}
}
https://sapui5.netweaver.ondemand.com/#docs/api/symbols/sap.ushell.services.CrossApplicationNavigation.html
SAP Fiori Launchpad for Developers, Navigation Concept
http://www.sdn.sap.com/irj/scn/go/portal/prtroot/docs/library/uuid/907ae317-cb47-3210-9bba-e1b5e70e5c79?QuickLink=index&overridelayout=true&59575491523067
I was having issues navigating from a Fiori elements app in to a deep page in a freestyle UI5 app and then answer from #user6649841 provided most the solution for my requirement.
In my instance, navigating from the elements list report (app "A") in to the target freestyle app (app "B") I didn't want the worklist/initial page in app B to display at all and instead go straight to the detail page without a flickering of the initial app screen.
The below worked for me, note though it doesn't solve the ugly URL issues. In my case I'm not fussed about it as my nav back will nav back to the elements list report (App A) and never show the worklist page in App B so the user will never make another search on top of this URL which would lead with inconsistent inner and outer keys
Component.js (at end of init function after all the standard sap code, but before router initialization):
var oComponentData = this.getComponentData();
var startupParams = oComponentData.startupParameters;
if (startupParams && startupParams.myQueryStringParamName && startupParams.myQueryStringParamName[0]) {
//In my case using hash changer as I dont want the original landing page (default route) to be
//in the history, so the detail page loads straight away and nav back will cause to nav back to App A
var hashChanger = sap.ui.core.routing.HashChanger.getInstance();
hashChanger.replaceHash("detailPage/" + startupParams.myQueryStringParamName[0]);
}
//initialise after the above so the new hash is set and it doesnt initially load the
//page assigned to the default route causing a flickering and nav slide effect
this.getRouter().initialize();
Looking at the UI5 SDK in UI5 1.48 and above in the initialize method of router you can pass in a boolean to tell it to ignore the initial hash so possibly can do a simpler implementation in newer releases of UI5
Is Component.js right place for handling the startup parameters?
Depends,if you have multiple views and you want to dynamically route based on the incoming parameters. Else you can handle in specific view also.
Your second question was not quite clear to me.
Nevertheless, if you want to only specific cases of startup parameters, then from Source App, set some flag to understand where is the request coming from and handle accordingly. So this way, your normal navigation won't be tampered.

PhantomJS change webpage content before evaluating

I'd like to either remove an HTML element or simply remove first N characters of a webpage before evaluating/rendering it.
Is there any way to do that?
It depends on multiple scenarios. I will only outline the steps for each combination of the answers to the following questions.
Is the piece of JS called onload (ol) or is the script block immediately evaluated (ie)?
Is it an inline script (is) or is the script loaded separately (src attribute) (ls)?
Does the script block also contain some code that should not be removed (nr) or can it be removed completely (rc)?
1. Script is loaded separately (ls) & code can be removed completely (rc)
Register to the onResourceRequested listener and request.abort() depending on the matched url.
2. Script is loaded separately (ls) & contains other code too (nr)
This can only be done when the following code blocks do not depend on the code that should not be removed (which is unlikely). This is most likely necessary for click events that are registered in the DOM.
In this case cancel the request like in 1., download the script through an XHR, remove the unwanted code parts and add code block to the DOM. For this to work, you would need to disable web security, because otherwise no resource can be requested if it is not on the same domain: --web-security=false.
3. Script is loaded with the DOM (is) & JS executed through onload (ol) & can be removed completely (rc)
This is probably very error prone. You would begin an Interval with setInterval(function(){}, 5) from a page.onInitialized callback. Inside the interval you would need to check if window.onload (or something else you can get your hands on) is set in the page context. You remove it, if it is indeed the function that you wanted to remove by checking window.onload.toString().match(/something/).
This can be done directly and completely inside the page context (inside page.evaluate).
4. Script is loaded with the DOM (is) & JS executed through onload (ol) & contains other code too (nr)
Begin like in 3., but instead of removing window.onload, you can do
eval("window.onload = " + window.onload.toString().replace(/something/,''))
5. Script is loaded with the DOM (is) & the script block immediately evaluated (ie)
You can load the page as an XHR, replace the text and apply the adjusted content to the page. This will essentially be a filled about:blank page. For this to work, you would need to disable web security, because otherwise no resource can be requested if it is not on the same domain: --web-security=false or --local-to-remote-url-access=true. This would also work for 3. and 4..
There is still one problem though. Pages don't use full URLs most of the time. So when a script or element refers to stuff.php PhantomJS cannot request it. When the page.content is set then the page URL is essentially about:blank and all requests with incomplete URLs point to file:///.... Obviously there are no such files. Those resources must be replaced with their full URL counterparts.
There are three types of such URLs:
//example.com/resource.php variable protocol
/resource.php variable protocol and domain
resource.php variable protocol, domain and path to resource
Complete example:
var page = require('webpage').create(),
url = 'http://www.example.com';
page.open(url, function(status) {
if (status !== 'success') {
console.log('Unable to access network');
} else {
var content = page.evaluate(function(url){
var xhr = new XMLHttpRequest();
xhr.open("GET", url, false);
xhr.send();
return xhr.responseText;
}, url);
page.render("test_example.png");
page.content = content.replace(/xample/g,"asy");
page.render("test_easy.png");
console.log("url "+page.url); // about:blank
phantom.exit();
}
});
You might want to look into proper manipulation techniques apart from the simple string replace.

How to get a Workspace's working sets information in a plugin?

I want to get the runtime working sets information of the current workspace. I have tried the method: IWorkingSet[] getWorkingSets() of the IWorkbenchPage
IWorkbenchPage page = PlatformUI.getWorkbench().getActiveWorkbenchWindow().getActivePage();
if (page != null) { IWorkingSet[] sets = page.getWorkingSets();}
but when I debug the code, the method returns nothing.
I am wondering am I use the right method to get a workspace's working sets information? If not, how to get the data?
The IWorkbenchPage getWorkingSets() method returns the 'Window Working Sets' in use for the page - this option is not enabled by default so returns null.
If you want all the working sets defined in the workbench you use:
IWorkingSetManager manager = PlatformUI.getWorkbench().getWorkingSetManager();
IWorkingSet [] allSets = manager.getAllWorkingSets();
The working set manager has many other methods for handling working sets.

root gets auto created in gwt app

Hi
I wrote code in one of the entry point class as:
if(RootPanel.get("fb-root") != null)
form = new BloodDonorForm(Constants.INSERT, null, Constants.FACEBOOK, Constants.BLOOD_DONOR_REGISTER_FORM);
else
form = new BloodDonorForm(Constants.INSERT, null, null, Constants.BLOOD_DONOR_REGISTER_FORM);
This used to work fine sometime back for sure (don't remember when I checked this last time). But now when I run the page in Firefox with firebug enabled I see the message like:
The "fb-root" div has not been created, auto-creating
So why is this done if it does not exist? I am sure I have tested this in past and this was not happening earlier.
This may be a change in GWT itself. That said, this isn't the best way to check for a dom element existing.
Instead, use Document.get().getElementById(String) to check for an element by id, and compare that with null. This will compile down to something very simple (probably just $doc.getElementById(id)), and won't create a widget yet (RootPanel is a widget) and the overhead that comes with that.