1 /*
2  * Copyright (C) 2013 Google Inc. All rights reserved.
3  *
4  * Redistribution and use in source and binary forms, with or without
5  * modification, are permitted provided that the following conditions are
6  * met:
7  *
8  *     * Redistributions of source code must retain the above copyright
9  * notice, this list of conditions and the following disclaimer.
10  *     * Redistributions in binary form must reproduce the above
11  * copyright notice, this list of conditions and the following disclaimer
12  * in the documentation and/or other materials provided with the
13  * distribution.
14  *     * Neither the name of Google Inc. nor the names of its
15  * contributors may be used to endorse or promote products derived from
16  * this software without specific prior written permission.
17  *
18  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
19  * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
20  * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
21  * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
22  * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
23  * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
24  * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
25  * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
26  * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
27  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
28  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
29  */
30 
31 #include "third_party/blink/renderer/modules/notifications/notification.h"
32 
33 #include <memory>
34 #include <utility>
35 
36 #include "base/unguessable_token.h"
37 #include "mojo/public/cpp/bindings/pending_remote.h"
38 #include "third_party/blink/public/common/notifications/notification_constants.h"
39 #include "third_party/blink/public/platform/task_type.h"
40 #include "third_party/blink/renderer/bindings/core/v8/serialization/serialized_script_value_factory.h"
41 #include "third_party/blink/renderer/bindings/core/v8/source_location.h"
42 #include "third_party/blink/renderer/bindings/modules/v8/v8_notification_action.h"
43 #include "third_party/blink/renderer/bindings/modules/v8/v8_notification_options.h"
44 #include "third_party/blink/renderer/core/dom/document.h"
45 #include "third_party/blink/renderer/core/dom/events/event.h"
46 #include "third_party/blink/renderer/core/dom/scoped_window_focus_allowed_indicator.h"
47 #include "third_party/blink/renderer/core/execution_context/execution_context.h"
48 #include "third_party/blink/renderer/core/frame/deprecation.h"
49 #include "third_party/blink/renderer/core/frame/local_frame.h"
50 #include "third_party/blink/renderer/core/frame/performance_monitor.h"
51 #include "third_party/blink/renderer/core/probe/core_probes.h"
52 #include "third_party/blink/renderer/modules/notifications/notification_data.h"
53 #include "third_party/blink/renderer/modules/notifications/notification_manager.h"
54 #include "third_party/blink/renderer/modules/notifications/notification_resources_loader.h"
55 #include "third_party/blink/renderer/modules/notifications/timestamp_trigger.h"
56 #include "third_party/blink/renderer/platform/bindings/exception_state.h"
57 #include "third_party/blink/renderer/platform/bindings/script_state.h"
58 #include "third_party/blink/renderer/platform/instrumentation/resource_coordinator/document_resource_coordinator.h"
59 #include "third_party/blink/renderer/platform/instrumentation/use_counter.h"
60 #include "third_party/blink/renderer/platform/runtime_enabled_features.h"
61 #include "third_party/blink/renderer/platform/wtf/assertions.h"
62 #include "third_party/blink/renderer/platform/wtf/functional.h"
63 #include "third_party/blink/renderer/platform/wtf/std_lib_extras.h"
64 
65 namespace blink {
66 
Create(ExecutionContext * context,const String & title,const NotificationOptions * options,ExceptionState & exception_state)67 Notification* Notification::Create(ExecutionContext* context,
68                                    const String& title,
69                                    const NotificationOptions* options,
70                                    ExceptionState& exception_state) {
71   // The Notification constructor may be disabled through a runtime feature when
72   // the platform does not support non-persistent notifications.
73   if (!RuntimeEnabledFeatures::NotificationConstructorEnabled()) {
74     exception_state.ThrowTypeError(
75         "Illegal constructor. Use ServiceWorkerRegistration.showNotification() "
76         "instead.");
77     return nullptr;
78   }
79 
80   // The Notification constructor may not be used in Service Worker contexts.
81   if (context->IsServiceWorkerGlobalScope()) {
82     exception_state.ThrowTypeError("Illegal constructor.");
83     return nullptr;
84   }
85 
86   if (!options->actions().IsEmpty()) {
87     exception_state.ThrowTypeError(
88         "Actions are only supported for persistent notifications shown using "
89         "ServiceWorkerRegistration.showNotification().");
90     return nullptr;
91   }
92 
93   if (options->hasShowTrigger()) {
94     exception_state.ThrowTypeError(
95         "ShowTrigger is only supported for persistent notifications shown "
96         "using ServiceWorkerRegistration.showNotification().");
97     return nullptr;
98   }
99 
100   auto* document = Document::DynamicFrom(context);
101   if (context->IsSecureContext()) {
102     UseCounter::Count(context, WebFeature::kNotificationSecureOrigin);
103     if (document) {
104       document->CountUseOnlyInCrossOriginIframe(
105           WebFeature::kNotificationAPISecureOriginIframe);
106     }
107   } else {
108     Deprecation::CountDeprecation(context,
109                                   WebFeature::kNotificationInsecureOrigin);
110     if (document) {
111       Deprecation::CountDeprecationCrossOriginIframe(
112           *document, WebFeature::kNotificationAPIInsecureOriginIframe);
113     }
114   }
115 
116   mojom::blink::NotificationDataPtr data =
117       CreateNotificationData(context, title, options, exception_state);
118   if (exception_state.HadException())
119     return nullptr;
120 
121   if (context->IsContextDestroyed()) {
122     exception_state.ThrowTypeError("Illegal invocation.");
123     return nullptr;
124   }
125 
126   Notification* notification = MakeGarbageCollected<Notification>(
127       context, Type::kNonPersistent, std::move(data));
128 
129   // TODO(https://crbug.com/595685): Make |token| a constructor parameter
130   // once persistent notifications have been mojofied too.
131   if (notification->tag().IsNull() || notification->tag().IsEmpty()) {
132     auto unguessable_token = base::UnguessableToken::Create();
133     notification->SetToken(unguessable_token.ToString().c_str());
134   } else {
135     notification->SetToken(notification->tag());
136   }
137 
138   notification->SchedulePrepareShow();
139 
140   if (document) {
141     if (auto* document_resource_coordinator =
142             document->GetResourceCoordinator()) {
143       document_resource_coordinator->OnNonPersistentNotificationCreated();
144     }
145   }
146 
147   return notification;
148 }
149 
Create(ExecutionContext * context,const String & notification_id,mojom::blink::NotificationDataPtr data,bool showing)150 Notification* Notification::Create(ExecutionContext* context,
151                                    const String& notification_id,
152                                    mojom::blink::NotificationDataPtr data,
153                                    bool showing) {
154   Notification* notification = MakeGarbageCollected<Notification>(
155       context, Type::kPersistent, std::move(data));
156   notification->SetState(showing ? State::kShowing : State::kClosed);
157   notification->SetNotificationId(notification_id);
158   return notification;
159 }
160 
Notification(ExecutionContext * context,Type type,mojom::blink::NotificationDataPtr data)161 Notification::Notification(ExecutionContext* context,
162                            Type type,
163                            mojom::blink::NotificationDataPtr data)
164     : ExecutionContextLifecycleObserver(context),
165       type_(type),
166       state_(State::kLoading),
167       data_(std::move(data)),
168       prepare_show_timer_(context->GetTaskRunner(TaskType::kMiscPlatformAPI),
169                           this,
170                           &Notification::PrepareShow) {
171   if (data_->show_trigger_timestamp.has_value()) {
172     show_trigger_ = TimestampTrigger::Create(static_cast<DOMTimeStamp>(
173         data_->show_trigger_timestamp.value().ToJsTime()));
174   }
175 }
176 
177 Notification::~Notification() = default;
178 
SchedulePrepareShow()179 void Notification::SchedulePrepareShow() {
180   DCHECK_EQ(state_, State::kLoading);
181 
182   prepare_show_timer_.StartOneShot(base::TimeDelta(), FROM_HERE);
183 }
184 
PrepareShow(TimerBase *)185 void Notification::PrepareShow(TimerBase*) {
186   DCHECK_EQ(state_, State::kLoading);
187   if (!GetExecutionContext()->IsSecureContext()) {
188     DispatchErrorEvent();
189     return;
190   }
191 
192   if (NotificationManager::From(GetExecutionContext())->GetPermissionStatus() !=
193       mojom::blink::PermissionStatus::GRANTED) {
194     DispatchErrorEvent();
195     return;
196   }
197 
198   loader_ = MakeGarbageCollected<NotificationResourcesLoader>(
199       WTF::Bind(&Notification::DidLoadResources, WrapWeakPersistent(this)));
200   loader_->Start(GetExecutionContext(), *data_);
201 }
202 
DidLoadResources(NotificationResourcesLoader * loader)203 void Notification::DidLoadResources(NotificationResourcesLoader* loader) {
204   DCHECK_EQ(loader, loader_.Get());
205 
206   mojo::PendingRemote<mojom::blink::NonPersistentNotificationListener>
207       event_listener;
208 
209   scoped_refptr<base::SingleThreadTaskRunner> task_runner =
210       GetExecutionContext()->GetTaskRunner(blink::TaskType::kInternalDefault);
211   listener_receiver_.Bind(event_listener.InitWithNewPipeAndPassReceiver(),
212                           task_runner);
213 
214   NotificationManager::From(GetExecutionContext())
215       ->DisplayNonPersistentNotification(token_, data_->Clone(),
216                                          loader->GetResources(),
217                                          std::move(event_listener));
218 
219   loader_.Clear();
220 
221   state_ = State::kShowing;
222 }
223 
close()224 void Notification::close() {
225   if (state_ != State::kShowing)
226     return;
227 
228   // Schedule the "close" event to be fired for non-persistent notifications.
229   // Persistent notifications won't get such events for programmatic closes.
230   if (type_ == Type::kNonPersistent) {
231     state_ = State::kClosing;
232     NotificationManager::From(GetExecutionContext())
233         ->CloseNonPersistentNotification(token_);
234     return;
235   }
236 
237   state_ = State::kClosed;
238 
239   NotificationManager::From(GetExecutionContext())
240       ->ClosePersistentNotification(notification_id_);
241 }
242 
OnShow()243 void Notification::OnShow() {
244   DispatchEvent(*Event::Create(event_type_names::kShow));
245 }
246 
OnClick(OnClickCallback completed_closure)247 void Notification::OnClick(OnClickCallback completed_closure) {
248   ExecutionContext* context = GetExecutionContext();
249   Document* document = Document::DynamicFrom(context);
250   if (document && document->GetFrame())
251     LocalFrame::NotifyUserActivation(document->GetFrame());
252   ScopedWindowFocusAllowedIndicator window_focus_allowed(GetExecutionContext());
253   DispatchEvent(*Event::Create(event_type_names::kClick));
254 
255   std::move(completed_closure).Run();
256 }
257 
OnClose(OnCloseCallback completed_closure)258 void Notification::OnClose(OnCloseCallback completed_closure) {
259   // The notification should be Showing if the user initiated the close, or it
260   // should be Closing if the developer initiated the close.
261   if (state_ == State::kShowing || state_ == State::kClosing) {
262     state_ = State::kClosed;
263     DispatchEvent(*Event::Create(event_type_names::kClose));
264   }
265   std::move(completed_closure).Run();
266 }
267 
DispatchErrorEvent()268 void Notification::DispatchErrorEvent() {
269   DispatchEvent(*Event::Create(event_type_names::kError));
270 }
271 
title() const272 String Notification::title() const {
273   return data_->title;
274 }
275 
dir() const276 String Notification::dir() const {
277   switch (data_->direction) {
278     case mojom::blink::NotificationDirection::LEFT_TO_RIGHT:
279       return "ltr";
280     case mojom::blink::NotificationDirection::RIGHT_TO_LEFT:
281       return "rtl";
282     case mojom::blink::NotificationDirection::AUTO:
283       return "auto";
284   }
285 
286   NOTREACHED();
287   return String();
288 }
289 
lang() const290 String Notification::lang() const {
291   return data_->lang;
292 }
293 
body() const294 String Notification::body() const {
295   return data_->body;
296 }
297 
tag() const298 String Notification::tag() const {
299   return data_->tag;
300 }
301 
image() const302 String Notification::image() const {
303   return data_->image.GetString();
304 }
305 
icon() const306 String Notification::icon() const {
307   return data_->icon.GetString();
308 }
309 
badge() const310 String Notification::badge() const {
311   return data_->badge.GetString();
312 }
313 
vibrate() const314 NavigatorVibration::VibrationPattern Notification::vibrate() const {
315   NavigatorVibration::VibrationPattern pattern;
316   if (data_->vibration_pattern.has_value()) {
317     pattern.AppendRange(data_->vibration_pattern->begin(),
318                         data_->vibration_pattern->end());
319   }
320 
321   return pattern;
322 }
323 
timestamp() const324 DOMTimeStamp Notification::timestamp() const {
325   return data_->timestamp;
326 }
327 
renotify() const328 bool Notification::renotify() const {
329   return data_->renotify;
330 }
331 
silent() const332 bool Notification::silent() const {
333   return data_->silent;
334 }
335 
requireInteraction() const336 bool Notification::requireInteraction() const {
337   return data_->require_interaction;
338 }
339 
data(ScriptState * script_state)340 ScriptValue Notification::data(ScriptState* script_state) {
341   const char* data = nullptr;
342   size_t length = 0;
343   if (data_->data.has_value()) {
344     // TODO(https://crbug.com/798466): Align data types to avoid this cast.
345     data = reinterpret_cast<const char*>(data_->data->data());
346     length = data_->data->size();
347   }
348   scoped_refptr<SerializedScriptValue> serialized_value =
349       SerializedScriptValue::Create(data, length);
350 
351   return ScriptValue(script_state->GetIsolate(),
352                      serialized_value->Deserialize(script_state->GetIsolate()));
353 }
354 
actions(ScriptState * script_state) const355 Vector<v8::Local<v8::Value>> Notification::actions(
356     ScriptState* script_state) const {
357   Vector<v8::Local<v8::Value>> result;
358   if (!data_->actions.has_value())
359     return result;
360 
361   const Vector<mojom::blink::NotificationActionPtr>& actions =
362       data_->actions.value();
363   result.Grow(actions.size());
364   for (wtf_size_t i = 0; i < actions.size(); ++i) {
365     NotificationAction* action = NotificationAction::Create();
366 
367     switch (actions[i]->type) {
368       case mojom::blink::NotificationActionType::BUTTON:
369         action->setType("button");
370         break;
371       case mojom::blink::NotificationActionType::TEXT:
372         action->setType("text");
373         break;
374       default:
375         NOTREACHED() << "Unknown action type: " << actions[i]->type;
376     }
377 
378     action->setAction(actions[i]->action);
379     action->setTitle(actions[i]->title);
380     action->setIcon(actions[i]->icon.GetString());
381     action->setPlaceholder(actions[i]->placeholder);
382 
383     // Both the Action dictionaries themselves and the sequence they'll be
384     // returned in are expected to the frozen. This cannot be done with
385     // WebIDL.
386     result[i] =
387         FreezeV8Object(ToV8(action, script_state), script_state->GetIsolate());
388   }
389 
390   return result;
391 }
392 
PermissionString(mojom::blink::PermissionStatus permission)393 String Notification::PermissionString(
394     mojom::blink::PermissionStatus permission) {
395   switch (permission) {
396     case mojom::blink::PermissionStatus::GRANTED:
397       return "granted";
398     case mojom::blink::PermissionStatus::DENIED:
399       return "denied";
400     case mojom::blink::PermissionStatus::ASK:
401       return "default";
402   }
403 
404   NOTREACHED();
405   return "denied";
406 }
407 
permission(ExecutionContext * context)408 String Notification::permission(ExecutionContext* context) {
409   // Permission is always denied for insecure contexts. Skip the sync IPC call.
410   if (!context->IsSecureContext())
411     return PermissionString(mojom::blink::PermissionStatus::DENIED);
412 
413   mojom::blink::PermissionStatus status =
414       NotificationManager::From(context)->GetPermissionStatus();
415 
416   // Permission can only be requested from top-level frames and same-origin
417   // iframes. This should be reflected in calls getting permission status.
418   //
419   // TODO(crbug.com/758603): Move this check to the browser process when the
420   // NotificationService connection becomes frame-bound.
421   if (status == mojom::blink::PermissionStatus::ASK) {
422     auto* document = Document::DynamicFrom(context);
423     LocalFrame* frame = document ? document->GetFrame() : nullptr;
424     if (!frame || frame->IsCrossOriginToMainFrame())
425       status = mojom::blink::PermissionStatus::DENIED;
426   }
427 
428   return PermissionString(status);
429 }
430 
requestPermission(ScriptState * script_state,V8NotificationPermissionCallback * deprecated_callback)431 ScriptPromise Notification::requestPermission(
432     ScriptState* script_state,
433     V8NotificationPermissionCallback* deprecated_callback) {
434   ExecutionContext* context = ExecutionContext::From(script_state);
435   Document* doc = Document::DynamicFrom(context);
436 
437   probe::BreakableLocation(context, "Notification.requestPermission");
438   if (!LocalFrame::HasTransientUserActivation(doc ? doc->GetFrame()
439                                                   : nullptr)) {
440     PerformanceMonitor::ReportGenericViolation(
441         context, PerformanceMonitor::kDiscouragedAPIUse,
442         "Only request notification permission in response to a user gesture.",
443         base::TimeDelta(), nullptr);
444   }
445 
446   // Sites cannot request notification permission from insecure contexts.
447   if (!context->IsSecureContext()) {
448     Deprecation::CountDeprecation(
449         context, WebFeature::kNotificationPermissionRequestedInsecureOrigin);
450   }
451 
452   // Sites cannot request notification permission from cross-origin iframes,
453   // but they can use notifications if permission had already been granted.
454   if (auto* document = Document::DynamicFrom(context)) {
455     LocalFrame* frame = document->GetFrame();
456     if (!frame || frame->IsCrossOriginToMainFrame()) {
457       Deprecation::CountDeprecation(
458           context, WebFeature::kNotificationPermissionRequestedIframe);
459     }
460   }
461 
462   return NotificationManager::From(context)->RequestPermission(
463       script_state, deprecated_callback);
464 }
465 
maxActions()466 uint32_t Notification::maxActions() {
467   return kNotificationMaxActions;
468 }
469 
DispatchEventInternal(Event & event)470 DispatchEventResult Notification::DispatchEventInternal(Event& event) {
471   DCHECK(GetExecutionContext()->IsContextThread());
472   return EventTarget::DispatchEventInternal(event);
473 }
474 
InterfaceName() const475 const AtomicString& Notification::InterfaceName() const {
476   return event_target_names::kNotification;
477 }
478 
ContextDestroyed()479 void Notification::ContextDestroyed() {
480   listener_receiver_.reset();
481 
482   state_ = State::kClosed;
483 
484   if (prepare_show_timer_.IsActive())
485     prepare_show_timer_.Stop();
486 
487   if (loader_)
488     loader_->Stop();
489 }
490 
HasPendingActivity() const491 bool Notification::HasPendingActivity() const {
492   // Non-persistent notification can receive events until they've been closed.
493   // Persistent notifications should be subject to regular garbage collection.
494   if (type_ == Type::kNonPersistent)
495     return state_ != State::kClosed;
496 
497   return false;
498 }
499 
Trace(Visitor * visitor)500 void Notification::Trace(Visitor* visitor) {
501   visitor->Trace(show_trigger_);
502   visitor->Trace(loader_);
503   EventTargetWithInlineData::Trace(visitor);
504   ExecutionContextLifecycleObserver::Trace(visitor);
505 }
506 
507 }  // namespace blink
508