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_dom_window.h"
50 #include "third_party/blink/renderer/core/frame/local_frame.h"
51 #include "third_party/blink/renderer/core/frame/performance_monitor.h"
52 #include "third_party/blink/renderer/core/probe/core_probes.h"
53 #include "third_party/blink/renderer/modules/notifications/notification_data.h"
54 #include "third_party/blink/renderer/modules/notifications/notification_manager.h"
55 #include "third_party/blink/renderer/modules/notifications/notification_resources_loader.h"
56 #include "third_party/blink/renderer/modules/notifications/timestamp_trigger.h"
57 #include "third_party/blink/renderer/platform/bindings/exception_state.h"
58 #include "third_party/blink/renderer/platform/bindings/script_state.h"
59 #include "third_party/blink/renderer/platform/instrumentation/resource_coordinator/document_resource_coordinator.h"
60 #include "third_party/blink/renderer/platform/instrumentation/use_counter.h"
61 #include "third_party/blink/renderer/platform/runtime_enabled_features.h"
62 #include "third_party/blink/renderer/platform/wtf/assertions.h"
63 #include "third_party/blink/renderer/platform/wtf/functional.h"
64 #include "third_party/blink/renderer/platform/wtf/std_lib_extras.h"
65 
66 namespace blink {
67 
Create(ExecutionContext * context,const String & title,const NotificationOptions * options,ExceptionState & exception_state)68 Notification* Notification::Create(ExecutionContext* context,
69                                    const String& title,
70                                    const NotificationOptions* options,
71                                    ExceptionState& exception_state) {
72   // The Notification constructor may be disabled through a runtime feature when
73   // the platform does not support non-persistent notifications.
74   if (!RuntimeEnabledFeatures::NotificationConstructorEnabled()) {
75     exception_state.ThrowTypeError(
76         "Illegal constructor. Use ServiceWorkerRegistration.showNotification() "
77         "instead.");
78     return nullptr;
79   }
80 
81   // The Notification constructor may not be used in Service Worker contexts.
82   if (context->IsServiceWorkerGlobalScope()) {
83     exception_state.ThrowTypeError("Illegal constructor.");
84     return nullptr;
85   }
86 
87   if (!options->actions().IsEmpty()) {
88     exception_state.ThrowTypeError(
89         "Actions are only supported for persistent notifications shown using "
90         "ServiceWorkerRegistration.showNotification().");
91     return nullptr;
92   }
93 
94   if (options->hasShowTrigger()) {
95     exception_state.ThrowTypeError(
96         "ShowTrigger is only supported for persistent notifications shown "
97         "using ServiceWorkerRegistration.showNotification().");
98     return nullptr;
99   }
100 
101   auto* window = DynamicTo<LocalDOMWindow>(context);
102   if (context->IsSecureContext()) {
103     UseCounter::Count(context, WebFeature::kNotificationSecureOrigin);
104     if (window) {
105       window->CountUseOnlyInCrossOriginIframe(
106           WebFeature::kNotificationAPISecureOriginIframe);
107     }
108   } else {
109     Deprecation::CountDeprecation(context,
110                                   WebFeature::kNotificationInsecureOrigin);
111     if (window) {
112       Deprecation::CountDeprecationCrossOriginIframe(
113           window, WebFeature::kNotificationAPIInsecureOriginIframe);
114     }
115   }
116 
117   mojom::blink::NotificationDataPtr data =
118       CreateNotificationData(context, title, options, exception_state);
119   if (exception_state.HadException())
120     return nullptr;
121 
122   if (context->IsContextDestroyed()) {
123     exception_state.ThrowTypeError("Illegal invocation.");
124     return nullptr;
125   }
126 
127   Notification* notification = MakeGarbageCollected<Notification>(
128       context, Type::kNonPersistent, std::move(data));
129 
130   // TODO(https://crbug.com/595685): Make |token| a constructor parameter
131   // once persistent notifications have been mojofied too.
132   if (notification->tag().IsNull() || notification->tag().IsEmpty()) {
133     auto unguessable_token = base::UnguessableToken::Create();
134     notification->SetToken(unguessable_token.ToString().c_str());
135   } else {
136     notification->SetToken(notification->tag());
137   }
138 
139   notification->SchedulePrepareShow();
140 
141   if (window) {
142     if (auto* document_resource_coordinator =
143             window->document()->GetResourceCoordinator()) {
144       document_resource_coordinator->OnNonPersistentNotificationCreated();
145     }
146   }
147 
148   return notification;
149 }
150 
Create(ExecutionContext * context,const String & notification_id,mojom::blink::NotificationDataPtr data,bool showing)151 Notification* Notification::Create(ExecutionContext* context,
152                                    const String& notification_id,
153                                    mojom::blink::NotificationDataPtr data,
154                                    bool showing) {
155   Notification* notification = MakeGarbageCollected<Notification>(
156       context, Type::kPersistent, std::move(data));
157   notification->SetState(showing ? State::kShowing : State::kClosed);
158   notification->SetNotificationId(notification_id);
159   return notification;
160 }
161 
Notification(ExecutionContext * context,Type type,mojom::blink::NotificationDataPtr data)162 Notification::Notification(ExecutionContext* context,
163                            Type type,
164                            mojom::blink::NotificationDataPtr data)
165     : ExecutionContextLifecycleObserver(context),
166       type_(type),
167       state_(State::kLoading),
168       data_(std::move(data)),
169       prepare_show_timer_(context->GetTaskRunner(TaskType::kMiscPlatformAPI),
170                           this,
171                           &Notification::PrepareShow),
172       listener_receiver_(this, context) {
173   if (data_->show_trigger_timestamp.has_value()) {
174     show_trigger_ = TimestampTrigger::Create(static_cast<DOMTimeStamp>(
175         data_->show_trigger_timestamp.value().ToJsTime()));
176   }
177 }
178 
179 Notification::~Notification() = default;
180 
SchedulePrepareShow()181 void Notification::SchedulePrepareShow() {
182   DCHECK_EQ(state_, State::kLoading);
183 
184   prepare_show_timer_.StartOneShot(base::TimeDelta(), FROM_HERE);
185 }
186 
PrepareShow(TimerBase *)187 void Notification::PrepareShow(TimerBase*) {
188   DCHECK_EQ(state_, State::kLoading);
189   if (!GetExecutionContext()->IsSecureContext()) {
190     DispatchErrorEvent();
191     return;
192   }
193 
194   if (NotificationManager::From(GetExecutionContext())->GetPermissionStatus() !=
195       mojom::blink::PermissionStatus::GRANTED) {
196     DispatchErrorEvent();
197     return;
198   }
199 
200   loader_ = MakeGarbageCollected<NotificationResourcesLoader>(
201       WTF::Bind(&Notification::DidLoadResources, WrapWeakPersistent(this)));
202   loader_->Start(GetExecutionContext(), *data_);
203 }
204 
DidLoadResources(NotificationResourcesLoader * loader)205 void Notification::DidLoadResources(NotificationResourcesLoader* loader) {
206   DCHECK_EQ(loader, loader_.Get());
207 
208   mojo::PendingRemote<mojom::blink::NonPersistentNotificationListener>
209       event_listener;
210 
211   scoped_refptr<base::SingleThreadTaskRunner> task_runner =
212       GetExecutionContext()->GetTaskRunner(blink::TaskType::kInternalDefault);
213   listener_receiver_.Bind(event_listener.InitWithNewPipeAndPassReceiver(),
214                           task_runner);
215 
216   NotificationManager::From(GetExecutionContext())
217       ->DisplayNonPersistentNotification(token_, data_->Clone(),
218                                          loader->GetResources(),
219                                          std::move(event_listener));
220 
221   loader_.Clear();
222 
223   state_ = State::kShowing;
224 }
225 
close()226 void Notification::close() {
227   if (state_ != State::kShowing)
228     return;
229 
230   // Schedule the "close" event to be fired for non-persistent notifications.
231   // Persistent notifications won't get such events for programmatic closes.
232   if (type_ == Type::kNonPersistent) {
233     state_ = State::kClosing;
234     NotificationManager::From(GetExecutionContext())
235         ->CloseNonPersistentNotification(token_);
236     return;
237   }
238 
239   state_ = State::kClosed;
240 
241   NotificationManager::From(GetExecutionContext())
242       ->ClosePersistentNotification(notification_id_);
243 }
244 
OnShow()245 void Notification::OnShow() {
246   DispatchEvent(*Event::Create(event_type_names::kShow));
247 }
248 
OnClick(OnClickCallback completed_closure)249 void Notification::OnClick(OnClickCallback completed_closure) {
250   ExecutionContext* context = GetExecutionContext();
251   auto* window = DynamicTo<LocalDOMWindow>(context);
252   if (window && window->GetFrame()) {
253     LocalFrame::NotifyUserActivation(
254         window->GetFrame(),
255         mojom::blink::UserActivationNotificationType::kInteraction);
256   }
257   ScopedWindowFocusAllowedIndicator window_focus_allowed(GetExecutionContext());
258   DispatchEvent(*Event::Create(event_type_names::kClick));
259 
260   std::move(completed_closure).Run();
261 }
262 
OnClose(OnCloseCallback completed_closure)263 void Notification::OnClose(OnCloseCallback completed_closure) {
264   // The notification should be Showing if the user initiated the close, or it
265   // should be Closing if the developer initiated the close.
266   if (state_ == State::kShowing || state_ == State::kClosing) {
267     state_ = State::kClosed;
268     DispatchEvent(*Event::Create(event_type_names::kClose));
269   }
270   std::move(completed_closure).Run();
271 }
272 
DispatchErrorEvent()273 void Notification::DispatchErrorEvent() {
274   DispatchEvent(*Event::Create(event_type_names::kError));
275 }
276 
title() const277 String Notification::title() const {
278   return data_->title;
279 }
280 
dir() const281 String Notification::dir() const {
282   switch (data_->direction) {
283     case mojom::blink::NotificationDirection::LEFT_TO_RIGHT:
284       return "ltr";
285     case mojom::blink::NotificationDirection::RIGHT_TO_LEFT:
286       return "rtl";
287     case mojom::blink::NotificationDirection::AUTO:
288       return "auto";
289   }
290 
291   NOTREACHED();
292   return String();
293 }
294 
lang() const295 String Notification::lang() const {
296   return data_->lang;
297 }
298 
body() const299 String Notification::body() const {
300   return data_->body;
301 }
302 
tag() const303 String Notification::tag() const {
304   return data_->tag;
305 }
306 
image() const307 String Notification::image() const {
308   return data_->image.GetString();
309 }
310 
icon() const311 String Notification::icon() const {
312   return data_->icon.GetString();
313 }
314 
badge() const315 String Notification::badge() const {
316   return data_->badge.GetString();
317 }
318 
vibrate() const319 VibrationController::VibrationPattern Notification::vibrate() const {
320   VibrationController::VibrationPattern pattern;
321   if (data_->vibration_pattern.has_value()) {
322     pattern.AppendRange(data_->vibration_pattern->begin(),
323                         data_->vibration_pattern->end());
324   }
325 
326   return pattern;
327 }
328 
timestamp() const329 DOMTimeStamp Notification::timestamp() const {
330   return data_->timestamp;
331 }
332 
renotify() const333 bool Notification::renotify() const {
334   return data_->renotify;
335 }
336 
silent() const337 bool Notification::silent() const {
338   return data_->silent;
339 }
340 
requireInteraction() const341 bool Notification::requireInteraction() const {
342   return data_->require_interaction;
343 }
344 
data(ScriptState * script_state)345 ScriptValue Notification::data(ScriptState* script_state) {
346   const char* data = nullptr;
347   size_t length = 0;
348   if (data_->data.has_value()) {
349     // TODO(https://crbug.com/798466): Align data types to avoid this cast.
350     data = reinterpret_cast<const char*>(data_->data->data());
351     length = data_->data->size();
352   }
353   scoped_refptr<SerializedScriptValue> serialized_value =
354       SerializedScriptValue::Create(data, length);
355 
356   return ScriptValue(script_state->GetIsolate(),
357                      serialized_value->Deserialize(script_state->GetIsolate()));
358 }
359 
actions(ScriptState * script_state) const360 Vector<v8::Local<v8::Value>> Notification::actions(
361     ScriptState* script_state) const {
362   Vector<v8::Local<v8::Value>> result;
363   if (!data_->actions.has_value())
364     return result;
365 
366   const Vector<mojom::blink::NotificationActionPtr>& actions =
367       data_->actions.value();
368   result.Grow(actions.size());
369   for (wtf_size_t i = 0; i < actions.size(); ++i) {
370     NotificationAction* action = NotificationAction::Create();
371 
372     switch (actions[i]->type) {
373       case mojom::blink::NotificationActionType::BUTTON:
374         action->setType("button");
375         break;
376       case mojom::blink::NotificationActionType::TEXT:
377         action->setType("text");
378         break;
379       default:
380         NOTREACHED() << "Unknown action type: " << actions[i]->type;
381     }
382 
383     action->setAction(actions[i]->action);
384     action->setTitle(actions[i]->title);
385     action->setIcon(actions[i]->icon.GetString());
386     action->setPlaceholder(actions[i]->placeholder);
387 
388     // Both the Action dictionaries themselves and the sequence they'll be
389     // returned in are expected to the frozen. This cannot be done with
390     // WebIDL.
391     result[i] =
392         FreezeV8Object(ToV8(action, script_state), script_state->GetIsolate());
393   }
394 
395   return result;
396 }
397 
PermissionString(mojom::blink::PermissionStatus permission)398 String Notification::PermissionString(
399     mojom::blink::PermissionStatus permission) {
400   switch (permission) {
401     case mojom::blink::PermissionStatus::GRANTED:
402       return "granted";
403     case mojom::blink::PermissionStatus::DENIED:
404       return "denied";
405     case mojom::blink::PermissionStatus::ASK:
406       return "default";
407   }
408 
409   NOTREACHED();
410   return "denied";
411 }
412 
permission(ExecutionContext * context)413 String Notification::permission(ExecutionContext* context) {
414   // Permission is always denied for insecure contexts. Skip the sync IPC call.
415   if (!context->IsSecureContext())
416     return PermissionString(mojom::blink::PermissionStatus::DENIED);
417 
418   mojom::blink::PermissionStatus status =
419       NotificationManager::From(context)->GetPermissionStatus();
420 
421   // Permission can only be requested from top-level frames and same-origin
422   // iframes. This should be reflected in calls getting permission status.
423   //
424   // TODO(crbug.com/758603): Move this check to the browser process when the
425   // NotificationService connection becomes frame-bound.
426   if (status == mojom::blink::PermissionStatus::ASK) {
427     auto* window = DynamicTo<LocalDOMWindow>(context);
428     LocalFrame* frame = window ? window->GetFrame() : nullptr;
429     if (!frame || frame->IsCrossOriginToMainFrame())
430       status = mojom::blink::PermissionStatus::DENIED;
431   }
432 
433   return PermissionString(status);
434 }
435 
requestPermission(ScriptState * script_state,V8NotificationPermissionCallback * deprecated_callback)436 ScriptPromise Notification::requestPermission(
437     ScriptState* script_state,
438     V8NotificationPermissionCallback* deprecated_callback) {
439   if (!script_state->ContextIsValid())
440     return ScriptPromise();
441 
442   ExecutionContext* context = ExecutionContext::From(script_state);
443 
444   probe::BreakableLocation(context, "Notification.requestPermission");
445   if (auto* window = DynamicTo<LocalDOMWindow>(context)) {
446     if (!LocalFrame::HasTransientUserActivation(window->GetFrame())) {
447       PerformanceMonitor::ReportGenericViolation(
448           context, PerformanceMonitor::kDiscouragedAPIUse,
449           "Only request notification permission in response to a user gesture.",
450           base::TimeDelta(), nullptr);
451     }
452 
453     // Sites cannot request notification permission from cross-origin iframes,
454     // but they can use notifications if permission had already been granted.
455     if (window->GetFrame()->IsCrossOriginToMainFrame()) {
456       Deprecation::CountDeprecation(
457           context, WebFeature::kNotificationPermissionRequestedIframe);
458     }
459   }
460 
461   // Sites cannot request notification permission from insecure contexts.
462   if (!context->IsSecureContext()) {
463     Deprecation::CountDeprecation(
464         context, WebFeature::kNotificationPermissionRequestedInsecureOrigin);
465   }
466 
467   return NotificationManager::From(context)->RequestPermission(
468       script_state, deprecated_callback);
469 }
470 
maxActions()471 uint32_t Notification::maxActions() {
472   return kNotificationMaxActions;
473 }
474 
DispatchEventInternal(Event & event)475 DispatchEventResult Notification::DispatchEventInternal(Event& event) {
476   DCHECK(GetExecutionContext()->IsContextThread());
477   return EventTarget::DispatchEventInternal(event);
478 }
479 
InterfaceName() const480 const AtomicString& Notification::InterfaceName() const {
481   return event_target_names::kNotification;
482 }
483 
ContextDestroyed()484 void Notification::ContextDestroyed() {
485   state_ = State::kClosed;
486 
487   if (prepare_show_timer_.IsActive())
488     prepare_show_timer_.Stop();
489 
490   if (loader_)
491     loader_->Stop();
492 }
493 
HasPendingActivity() const494 bool Notification::HasPendingActivity() const {
495   // Non-persistent notification can receive events until they've been closed.
496   // Persistent notifications should be subject to regular garbage collection.
497   if (type_ == Type::kNonPersistent)
498     return state_ != State::kClosed;
499 
500   return false;
501 }
502 
Trace(Visitor * visitor) const503 void Notification::Trace(Visitor* visitor) const {
504   visitor->Trace(show_trigger_);
505   visitor->Trace(loader_);
506   visitor->Trace(listener_receiver_);
507   EventTargetWithInlineData::Trace(visitor);
508   ExecutionContextLifecycleObserver::Trace(visitor);
509 }
510 
511 }  // namespace blink
512