-
Notifications
You must be signed in to change notification settings - Fork 2
Show or hide close button based on destination parameter #109
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Conversation
@@ -41,7 +41,7 @@ function collabora_online_theme(): array { | |||
'accessToken' => 'test', | |||
'accessTokenTtl' => '86400', | |||
'iFrameStyle' => 'width:95%;', | |||
'closebutton' => '', | |||
'closeButtonUrl' => '', |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The goal here was to keep the changes to template and js to a minimum.
We should rethink this in a follow-up.
@@ -108,11 +108,14 @@ function collabora_online_entity_operation(EntityInterface $entity): array { | |||
return []; | |||
} | |||
|
|||
$options = []; | |||
$options['query'] = \Drupal::destination()->getAsArray(); | |||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I looked for examples in core how they populate the 'destination' parameter.
I found only one case where they explicitly use the current url, in other cases they use that destination service.
The main difference is this (I think): If the current page already has a destination parameter, it will be kept for the link.
@@ -25,7 +25,7 @@ function postReady() { | |||
}); | |||
} | |||
|
|||
function receiveMessage(hasCloseButton, event) { | |||
function receiveMessage(close_button_url, event) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We could use lowerCamelCase here, for consistency with the rest of the script.
In the end we should redo all of it.
* | ||
* @see \Drupal\Core\Form\ConfirmFormHelper::buildCancelLink() | ||
* @see \Drupal\Core\EventSubscriber\RedirectResponseSubscriber::getDestinationAsAbsoluteUrl() | ||
*/ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It would have been more straightforward to simply read the destination parameter with javascript. However, I have not found this done in Drupal core.
Reading the parameter in php makes sure that we apply the same processing as in other places.
This said, I did look for other places that parse the destination parameter, and it does not look very standardized.
if (closebutton == 'true') { | ||
options = { closebutton: true }; | ||
} | ||
const close_button_url = {{ closeButtonUrl|json_encode|raw }}; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is the most "correct" way to escape and quote values for js.
An alternative is '{{ varname|e('js') }}'
, but this also escapes ':' because it is meant for a broader purpose.
Originally we are just doing "{{ varname }}"
but this is wrong, e.g. "{{ '"' }}"
leads to broken js.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
yes, you can verify with js console: 'http\u003A\/\/' === 'http://'
is true.
The |e('js')
is more noisy because it is designed for a wider range of scenarios.
Also, you need to remember that you must use double quotes "{{ var|e('js') }}"
, to support all possible strings.
In this case we usually won't have double quotes in the url, but better to always do the right thing.
I still prefer |json_encode|raw
, it relies on logic instead of how a specific twig feature behaves.
1d4ee8b
to
f3c190d
Compare
f3c190d
to
58f12bc
Compare
This line should have been removed in c95f8ec.
protected function getDestinationUrl(Request $request): ?Url { | ||
$destination = (string) $request->query->get('destination'); | ||
if ( | ||
!$destination || | ||
UrlHelper::isExternal($destination) || | ||
// Instead of ltrim(), simply discard a destination with missing or | ||
// unexpected leading slashes. | ||
!str_starts_with($destination, '/') || | ||
str_starts_with($destination, '//') | ||
) { | ||
return NULL; | ||
} | ||
$parsed = UrlHelper::parse($destination); | ||
$options = [ | ||
'query' => $parsed['query'], | ||
'fragment' => $parsed['fragment'], | ||
'absolute' => TRUE, | ||
]; | ||
return Url::fromUserInput($parsed['path'], $options); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This approach seems too defensive. There is similar code in core, but for legacy urls. I don't care about legacy in our code.
Let's just use the destination service:
try {
@see \Drupal\Core\Routing\RedirectDestinationInterface::get
$destination_url = \Drupal\Core\Url::fromUserInput(\Drupal::destination()->get())->setAbsolute();
}
catch (\InvalidArgumentException) {
$destination_url = NULL;
}
We are not here to provide all the possible scenarios when users mingle with the redirect url. What we care is only the security part.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
By using the destination service, we get a value (and thus a close button) even if the query parameter is empty.
This is because Destination falls back to the current url.
It seems to me that:
- The destination service is meant for code that wants to add a destination parameter to a url.
- The actual redirect is done by other code such as RedirectResponseSubscriber, which does not use this service, and which I am replicating here more or less.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
By using the destination service, we get a value (and thus a close button) even if the query parameter is empty.
This is because Destination falls back to the current url.
(and a failing test)
* @testWith ["view", "/admin/structure"] | ||
* ["edit", "/admin/structure"] | ||
* ["edit", "/non/existing/path?x=y"] | ||
* ["edit", "malformed/path", false] | ||
* ["edit", "//multislash/path", false] | ||
* ["edit", "https://example.com/hello", false] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We said already that data providers eat time in these non unit test cases. Let's avoid this. How can we avoid the install loop?
Furthermore let's test only the exception case (no destination), external case (fallbacks to /) and one working one.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
keep in mind this is ExistingSite, so no install loop.
The data providers are probably not much different from calling multiple cases from the same test method.
The duration is then from opening the iframe and loading the editor.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Indeed even with ExistingSite, by combining all cases into one, for me the time is reduced from 15s to 5s.
One reason is that we don't have to repeat the create user, login, and create document media.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Furthermore let's test only the exception case (no destination), external case (fallbacks to /) and one working one.
We do want to cover preview vs edit:
- The UI looks a bit different, and the close button is placed differently. (this is a bit of a canary test, telling us if something changes in CollaboraOnline)
- In our own code, the logic that determines close button or not also determines preview vs edit.
I would also like to cover malformed urls, but ok I removed it.
// Repeat the click until the button disappears. | ||
$this->getSession()->getPage()->waitFor(10000, function () use ($close_button, &$n) { | ||
// On successful click, the user is redirected to the destination url. | ||
// As a consequence, the close button disappears, and subsequent calls | ||
// to $close_button->click() trigger a NoSuchElement exception. | ||
// By relying on this exception, we avoid any kind of race condition that | ||
// could lead to rare random test failures. | ||
try { | ||
$close_button->click(); | ||
} | ||
catch (NoSuchElement) { | ||
return TRUE; | ||
} | ||
return FALSE; | ||
}); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Odd, let's remove it. We click once, we need to be redirected.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's not working for me, unless I wait e.g. 200ms before clicking the button.
I tried to find conditions to check whether the button is clickable, but any condition I could find (including post message telling us we are ready) is already true when this happens.
if (closebutton == 'true') { | ||
options = { closebutton: true }; | ||
} | ||
const close_button_url = {{ closeButtonUrl|json_encode|raw }}; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No description provided.