File Coverage

blib/lib/Plack/Middleware/iPhone.pm
Criterion Covered Total %
statement 63 78 80.7
branch 12 26 46.1
condition 2 7 28.5
subroutine 10 11 90.9
pod 2 5 40.0
total 89 127 70.0


line stmt bran cond sub pod time code
1             #
2             # This file is part of Plack-Middleware-iPhone
3             #
4             # This software is copyright (c) 2010 by Patrick Donelan.
5             #
6             # This is free software; you can redistribute it and/or modify it under
7             # the same terms as the Perl 5 programming language system itself.
8             #
9             package Plack::Middleware::iPhone;
10             BEGIN {
11 1     1   52859 $Plack::Middleware::iPhone::VERSION = '1.102060';
12             }
13              
14             # ABSTRACT: Make your html more iPhone friendly
15              
16 1     1   9 use warnings;
  1         2  
  1         31  
17 1     1   7 use strict;
  1         2  
  1         36  
18 1     1   7 use parent qw( Plack::Middleware );
  1         1  
  1         7  
19 1     1   23654 use Plack::Util::Accessor qw( manifest icon startup_image tidy viewport statusbar );
  1         3  
  1         4  
20              
21             sub new {
22 1     1 1 184 my $class = shift;
23 1         7 my $self = $class->SUPER::new(@_);
24              
25 1 50       23 $self->write_manifest if $self->manifest;
26              
27 1         97 return $self;
28             }
29              
30             sub call {
31 1     1 1 27360 my $self = shift;
32 1         3 my $env = shift;
33 1         14 my $res = $self->app->($env);
34              
35             # Buffer the entire html response (surely there's a better way..)
36 1         16 my $whole_response = '';
37              
38             $self->response_cb(
39             $res,
40             sub {
41 1     1   26 my $res = shift;
42 1         7 my $h = Plack::Util::headers( $res->[1] );
43              
44 1 50       43 if ( $h->get('Content-Type') =~ m!^text/html! ) {
45             return sub {
46 2         44 my $chunk = shift;
47 2 100       15 return unless defined $chunk;
48              
49 1         2 $whole_response .= $chunk;
50 1 50       8 if ( $chunk =~ m{}i ) {
51 1         5 return $self->filter($whole_response);
52             }
53 1         73 };
54             }
55             }
56 1         15 );
57             }
58              
59             sub filter {
60 1     1 0 2 my $self = shift;
61 1         3 my $chunk = shift;
62 1         1244 require HTML::DOM;
63 1 50       1447427 my $dom = new HTML::DOM or return $chunk;
64 1         298 $dom->write($chunk);
65 1         2198 $dom->close;
66              
67 1 50       435 if ( my $manifest = $self->manifest ) {
68 0         0 $dom->documentElement->setAttribute( 'manifest', $manifest );
69             }
70              
71 1         13 my $head = $dom->getElementsByTagName('head')->[0];
72 1   50     201 my @meta = (
      50        
73             [ name => 'viewport', content => $self->viewport || 'width = device-width' ],
74             [ name => 'apple-mobile-web-app-capable', content => 'yes' ],
75             [ name => 'apple-mobile-web-app-status-bar-style', content => $self->statusbar || 'gray' ],
76             );
77 1         24 for my $attrs (@meta) {
78 3         4463 $head->appendChild( $self->el( $dom, 'meta', @$attrs ) );
79             }
80              
81 1         1136 my %rel_links = map { $_->rel => 1 } $head->getElementsByTagName('link');
  0         0  
82 1         165 my @links;
83 1 50       7 push @links, { rel => "apple-touch-icon", href => $self->icon } if $self->icon;
84 1 50       17 push @links, { rel => "apple-touch-startup-image", href => $self->startup_image } if $self->startup_image;
85              
86 1         12 for my $link_attrs (@links) {
87 2 50       1233 if ( $rel_links{ $link_attrs->{rel} } ) {
88 0         0 warn "$link_attrs->{rel} link already exists";
89             }
90             else {
91 2         7 $head->appendChild( $self->el( $dom, 'link', %$link_attrs ) );
92             }
93             }
94              
95 1         1118 my $html = $dom->innerHTML;
96              
97 1 50       1601 if ( $self->tidy ) {
98 1         1019 require UNIVERSAL::require;
99 1 50       1614 if ("HTML::Tidy"->require) {
100 0         0 my $tidy = HTML::Tidy->new( { output_html => 1, indent => 'auto', tidy_mark => 'no' } );
101 0         0 $html = $tidy->clean($html);
102             } else {
103 1         43 warn "HTML::Tidy not available"
104             }
105             }
106              
107 1         94 return $html;
108             }
109              
110             sub el {
111 5     5 0 8 my $self = shift;
112 5         6 my $dom = shift;
113 5         7 my $type = shift;
114 5         12 my @attrs = @_;
115 5         17 my $el = $dom->createElement($type);
116 5         251 while ( my ( $attr, $val ) = splice @attrs, 0, 2 ) {
117 10         1712 $el->$attr($val);
118             }
119 5         1248 return $el;
120             }
121              
122             sub write_manifest {
123 0     0 0   my $self = shift;
124              
125 0           require Digest::MD5;
126 0           require File::Slurp;
127 0           my $manifest = $self->manifest;
128              
129             # Write the manifest once, at compile time
130 0 0         open my $fh, '>', $manifest or die "Unable to write manifest $manifest. $!";
131 0           $fh->print("CACHE MANIFEST\n");
132 0           for my $file (<*.*>) {
133              
134             # Don't put manifest or app.psgi in manifest file
135 0 0 0       next if $file eq $manifest or $file =~ m/\.psgi$/;
136              
137             # Write MD5 hash so that manifest changes whenever files change (auto cache updating)
138 0           $fh->print("$file #");
139 0           $fh->print( Digest::MD5::md5_hex( File::Slurp::read_file($file) ) . "\n" );
140             }
141             }
142              
143             1;
144              
145              
146              
147             =pod
148              
149             =head1 NAME
150              
151             Plack::Middleware::iPhone - Make your html more iPhone friendly
152              
153             =head1 VERSION
154              
155             version 1.102060
156              
157             =head1 SYNOPSIS
158              
159             # iPhone compatible directory listing..
160             plackup -MPlack::App::Directory -e 'builder { enable iPhone; Plack::App::Directory->new }'
161            
162             # m.search.CPAN.org
163             plackup -MPlack::App::Proxy -e 'builder {enable iPhone; Plack::App::Proxy->new(remote => "http://search.cpan.org/") }'
164            
165             # Or in your app.psgi
166             use Plack::Builder;
167             builder {
168             enable "iPhone",
169             tidy => 1,
170             manifest => 'app.manifest',
171             viewport => 'initial-scale = 1, maximum-scale = 1.5, width = device-width',
172             statusbar => 'black-translucent',
173             startup_image => 'loading.png',
174             icon => 'icon.png';
175             $app;
176             }
177              
178             =head1 DESCRIPTION
179              
180             Plack::Middleware::iPhone does on-the-fly rewriting of any html content returned by your app (mostly just the head block)
181             to make it play nicer with iPhones.
182              
183             This is a borderline ACME module. For real
184             L
185             mobile web apps you should be writing the HTML yourself.
186              
187             =head1 ATTRIBUTES
188              
189             =head2 icon
190              
191             A 57x57 image icon that the iPhone will display as a shortcut to your app if you add it to your Home Screen
192             via the "Add to Home Screen" function.
193              
194             =head2 startup_image
195              
196             A 320x460 PNG image that is displayed while your app is loading. If this is not set, the iPhone automatically
197             uses a screenshot of the most recent app state.
198              
199             =head2 statusbar
200              
201             Sets the C meta tag, which controls the status bar appearance when yourself
202             app is launched from a Home icon shortcut.
203              
204             Valid options are:
205              
206             =over 4
207              
208             =item *
209              
210             gray (default)
211              
212             =item *
213              
214             black
215              
216             =item *
217              
218             black-translucent
219              
220             =back
221              
222             =head2 viewport
223              
224             Sets the viewport meta tag, which determines how wide your iPhone thinks the screen is and scaling options.
225              
226             See
227             L
228             for more information.
229              
230             =head2 manifest
231              
232             Automatically generates a manifest file for your application (with whatever name you pass in), and sets the
233             C attribute on the html root tag, which triggers your iPhone to start using offline HTML Web App caching.
234              
235             See L for more information
236              
237             =head2 tidy
238              
239             Run the HTML through L
240              
241             =head1 SEE ALSO
242              
243             L, Jonathan Stark (freely available).
244              
245             =head1 AUTHOR
246              
247             Patrick Donelan
248              
249             =head1 COPYRIGHT AND LICENSE
250              
251             This software is copyright (c) 2010 by Patrick Donelan.
252              
253             This is free software; you can redistribute it and/or modify it under
254             the same terms as the Perl 5 programming language system itself.
255              
256             =cut
257              
258              
259             __END__