Fremdkoerper - Javascript Objekte in Perl

Max Maischein

Frankfurt.pm

Übersicht

  • Motivation

  • tie

  • overload

  • Transparente Objekte mit tie+overload

Ziel

  • Verständnis von tie

  • Verständnis von overload

  • Verständnis von AUTOLOAD

  • Ideen für Objekte ausserhalb von Perl

Motivation

  • Automatisierung von Firefox

  • Firefox ist in Javascript geschrieben

  • Zugriff aus Perl

  • ... nicht ueber Javascript

Anwendungsstruktur

 1:  Perl <-> FireFox     <-> Website

Anwendungsstruktur

 1:  Perl <-> FireFox     <-> Website

Javascript:

 1:  alert( window.tab
 2:         .linkedBrowser
 3:         .document
 4:         .innerHTML)

Anwendungsstruktur

 1:  Perl <-> FireFox     <-> Website

Javascript:

 1:  alert( window.tab
 2:         .linkedBrowser
 3:         .document
 4:         .innerHTML)

Perl:

 1:  print $window->{tab}
 2:        ->{linkedBrowser}
 3:        ->{document}
 4:        ->{innerHTML}

Anwendungsstruktur

 1:  Perl <-> FireFox     <-> Website

Javascript:

 1:  alert( window.tab
 2:         .linkedBrowser
 3:         .document
 4:         .innerHTML)

Perl:

 1:  print $window->{tab}
 2:        ->{linkedBrowser}
 3:        ->{document}
 4:        ->{innerHTML}

Totale Informationskontrolle

$window->...

  • Datenzugriffe

     1:  $window->{title}
  • Datenverwendung

     1:  $browser = $window->{linkedBrowser}

TIE

Überblick

  • Definition

  • Beispiel

  • Vorteile / Nachteile

TIE

Definition

tie leitet den Zugriff auf eine Variable um auf Perl Code.

TIE

Definition

tie leitet den Zugriff auf eine Variable um auf Perl Code.

Syntax

 1:  tie %myhash, 'Meine::Tie::Klasse', ...
 2:
 3:  tie @lines, 'Tie::File' => 'einedatei.txt';
 4:
 5:  tie $zeilen, 'Tie::Cycle' => 1,5;

Beispiel

Problemstellung:

(Web)Server-Anwendung mit Usern

 1:  user_id | name   | seit     | zuletzt
 2:  1       | admin  | 1.1.1970 | 6.06.2010
 3:  2       | anonym | 1.1.1970 | -
 4:  3       | Max    | 1.1.1970 | 6.06.2010
 5:  ...

Beispiel (2)

 1:  my $res = $dbh->selectall_arrayref(<<'SQL',
 2:                                    {Slice => {}});
 3:      SELECT *
 4:      FROM users
 5:  SQL
 6:  my %users = map { $_->{user_id} => { %{ $_ } }}
 7:              @{ $res }; 
 8:
 9:  __END__

Beispiel (2)

 1:  my $res = $dbh->selectall_arrayref(<<'SQL',
 2:                                     {Slice => {}});
 3:      SELECT *
 4:      FROM users
 5:  SQL
 6:  my %users = map { $_->{user_id} => { %{ $_ } }}
 7:              @{ $res }; 
 8:  
 9:  __END__
10:  %users = (
11:    '1' => {
12:      'seit' => '1970-01-01 00:00:01',
13:      'name' => 'admin',
14:      'zuletzt' => '1970-01-01 00:00:01',
15:      'user_id' => '1'
16:    },
17:  ...

Beispiel (2)

 1:  my $res = $dbh->selectall_arrayref(<<'SQL', {Slice => {}});
 2:      SELECT *
 3:      FROM users
 4:  SQL
 5:  my %users = map { $_->{user_id} => { %{ $_ } }}
 6:              @{ $res };

Nachteile:

  • Langsam

  • Viele Daten abgefragt für wenig Nutzung

Vorteile

  • Exisitiert

Beispiel (3)

 1:  my $res = $dbh->selectall_arrayref(<<'SQL', {Slice => {}});
 2:      SELECT *
 3:      FROM users
 4:      WHERE user_id = ?
 5:  SQL

Vorteile

  • Weniger Datentransfer

Nachteile:

  • Existiert nicht

  • Umstellung des Codes von Hash-Zugriff auf Prozedur

tie

Idee: Ersetzen des Prozeduraufrufs durch ein Hash

Zugriff auf

 1:  $users{ 1 }

... soll

 1:  fetch_user(1)

aufrufen

tie

Idee: Ersetzen des Prozeduraufrufs durch ein Hash:

 1:  tie %users, 'MyApp::Tie::Users', ...
 2:  
 3:  package MyApp::Tie::Users;
 4:    sub TIEHASH { ... }
 5:    sub FETCH { ... }
 6:    sub STORE { ... }
 7:    sub FIRSTKEY { ... }
 8:    sub NEXTKEY  { ... }

tie

Idee: Ersetzen des Prozeduraufrufs durch ein Hash:

 1:  tie %users, 'MyApp::Tie::Users', ...
 2:    sub TIEHASH { ... }

Deklaration / Konstruktor

tie

Idee: Ersetzen des Prozeduraufrufs durch ein Hash:

 1:  tie %users, 'MyApp::Tie::Users', ...
 2:    sub TIEHASH { ... }
 3:    sub FETCH { ... }

Methode für Lesezugriff

tie

Idee: Ersetzen des Prozeduraufrufs durch ein Hash:

 1:  tie %users, 'MyApp::Tie::Users', ...
 2:    sub TIEHASH { ... }
 3:    sub FETCH { ... }
 4:    sub STORE { ... }

Methode für Schreibzugriff

tie

Idee: Ersetzen des Prozeduraufrufs durch ein Hash:

 1:  tie %users, 'MyApp::Tie::Users', ...
 2:    sub TIEHASH { ... }
 3:    sub FETCH { ... }
 4:    sub STORE { ... }
 5:    sub FIRSTKEY { ... }
 6:    sub NEXTKEY  { ... }

Methoden für Schlüsselaufzählung

tie

 1:  sub FETCH {
 2:      ...
 3:      SELECT *
 4:      FROM users
 5:      WHERE user_id = ?
 6:      ...      
 7:  }

Achtung: Dieser Ansatz geht nur bei einer vorhandenen Schlüsselspalte.

Ansonsten könnten wir Arrayreferenzen zurückliefern.

tie

 1:  package MyApp::Tie::Users;
 2:  use parent 'Tie::Hash';
 3:  ...

Tie::Hash liefert das normale Verhalten eines Hashes als Grundlage

tie

 1:  package MyApp::Tie::Users;
 2:  use parent 'Tie::Hash';
 3:  ...
 4:  sub TIEHASH {
 5:    my ($class,$dbh) = @_;
 6:    my $self = {
 7:        dbh => $dbh,
 8:    };
 9:    bless $self, $class
10:  }

tie FETCH

 1:  sub FETCH {
 2:    my ($self,$key) = @_;
 3:    my $sth = $dbh->prepare_cached(<<'SQL');
 4:        SELECT *
 5:        FROM users
 6:        WHERE user_id = ?
 7:  SQL
 8:    my $res = $sth->execute($key);
 9:    if ($res) {
10:        $res = $sth->fetchall_arrayref({});
11:        return $res->[0]
12:    }
13:    return undef
14:  }

tie STORE

 1:  sub STORE {
 2:    my ($self,$key,$value) = @_;
 3:    croak "Can only store (user) hashrefs"
 4:        unless ref $value eq 'HASH';
 5:    croak "Key '$key' inconsistent with user id '$value->{user_id}'"
 6:        if (defined $value->{user_id} and $key ne $value->{user_id});
 7:    
 8:    my $sth = $self->{dbh}->prepare_cached(<<'SQL');
 9:        INSERT INTO users
10:        (user_id,name,seit,zuletzt) VALUES (?,?,?,?)
11:  SQL
12:    my $res = $sth->execute(@{ $value }{qw[user_id name seit zuletzt]});
13:    return $value
14:  }

Demo (2)

Demo

02-tiehash.pl

Verwendung

 1:  use MyApp::Tie::Users;
 2:  my $dbh = DBI->connect(...);
 3:  tie my %users, 'MyApp::Tie::Users'
 4:      => $dbh,
 5:  ;  
 6:  print $users{2}->{name};

Vorteile

  • Leicht ein/ersetzbar

     1:  tie my %users, 'MyApp::Tie::Users'
     2:      => 'dbi:SQLite:users.sqlite',
     3:         'users'
     4:  ;

Vorteile

  • Leicht ein/ersetzbar

     1:  #tie my %users, 'MyApp::Tie::Users'
     2:  #    => 'dbi:SQLite:users.sqlite',
     3:  #       'users'
     4:  #;
     5:  my %users = { 2 => {name => 'Max', ...}, ... };
  • Fehlerquelle leicht lokalisierbar

  • Tests leicht implementierbar

Nachteile

tie ist relativ langsam im Vergleich zum direkten Variablenzugriff und auch im Vergleich zum Methodenaufruf

 1:    my $tied_obj = tied %users_tied;
 2:
 3:    cmpthese(
 4:        -10, {
 5:            'tied'   => sub { $users_tied{ rand(6) } },
 6:            'object' => sub { $tied_obj->FETCH(rand(6)) },
 7:            'plain'  => sub { $users{ rand(6) } },
 8:            'full'   => sub { fetch_all_and_return() },
 9:        }
10:    );

Nachteile (Benchmark)

05-benchmark-tiehash-plain.pl

Nachteile (Benchmark)

 1:             Rate   full   tied object  plain
 2:  full     2444/s     --   -74%   -75%   -99%
 3:  tied     9309/s   281%     --    -6%   -96%
 4:  object   9886/s   304%     6%     --   -95%
 5:  plain  215698/s  8725%  2217%  2082%     --

Ursachen für die Geschwindigkeitsunterschiede

05-benchmark-tiehash-plain.pl

 1:             Rate   full   tied object  plain
 2:  full     2444/s     --   -74%   -75%   -99%
 3:  tied     9309/s   281%     --    -6%   -96%

Datenmenge und ->prepared Statement

Ursachen für die Geschwindigkeitsunterschiede

05-benchmark-tiehash-plain.pl

 1:             Rate   full   tied object  plain
 2:  full     2444/s     --   -74%   -75%   -99%
 3:  tied     9309/s   281%     --    -6%   -96%
 4:  object   9886/s   304%     6%     --   -95%

6% für tie

Ursachen für die Geschwindigkeitsunterschiede

Warum?

Ursachen für die Geschwindigkeitsunterschiede

Warum?

 1:  my $max = $users{ 2 };
 2:  ...
 3:  
 4:  $users{ 2 } => tied(%users)->FETCH(2)
 5:  Perl -> 'tie magic' -> Perl -> Datenbank -> Daten

1. Abfangen des Zugriffs

2. Methodenaufruf

3. Perl Code

Tie mit anderen Datentypen

tie geht auch mit Skalaren

TIESCALAR, STORE, FETCH

Beispiel für Skalare

 1:  sub FETCH { 'Max' }

Arrays

... und Arrays

TIEARRAY, STORE, FETCH

tie mit Arrays

Bessere Verwendung für die Userdatenbank:

 1:  push @users, { name => 'Max' };

Verwendung von tie

  • Tie::File - Datei als Array mit Zeilen als Array-Elementen

  • Tie::DBI - gerade gesehen

  • Tie::DBM - Hash auf Festplatte statt im Speicher

  • Tie::Memcached - Hash in memcached statt im (eigenen) Speicher

  • <Tie::Cycle> - ...

Zusammenfassung

tie leitet den Variablenzugriff um auf Perl Code.

Zusammenfassung

tie leitet den Variablenzugriff um auf Perl Code.

Syntax

 1:  tie %myhash, 'MyApp::Tie::Users', ...
 2:
 3:  tie @lines, 'Tie::File' => 'einedatei.txt';
 4:
 5:  tie $zeilen, 'Tie::Cycle' => 1,5;

Zusammenfassung

tie leitet den Variablenzugriff um auf Perl Code.

Syntax

 1:  tie %myhash, 'MyApp::Tie::Users', ...
 2:
 3:  tie @lines, 'Tie::File' => 'einedatei.txt';
 4:
 5:  tie $zeilen, 'Tie::Cycle' => 1,5;

tie ist langsamer als andere Zugriffsformen.

Zusammenfassung (2)

  • Tie einrichten (TIEHASH, TIESCALAR, TIEARRAY)

  • Lesen (FETCH)

  • Schreiben (STORE)

  • Löschen (DELETE)

  • Aufzählen (FIRSTKEY, NEXTKEY)

Weitere Dokumentation

  • Tie::Hash

  • Tie::StdHash

  • perltie

overload

Übersicht

  • tie

  • overload

Definition

Das overload-Pragma leitet die Werteverwendung auf Perl-Code um.

  • Verschiedene Klassen von Operatoren

  • Interpolation

  • Arithmetische Operationen ( +-*/ )

  • Vergleichsoperationen (<=> cmp)

  • Dereferenzierung ( ->)

Grundlegende Verwendung

 1:    package MyApp::User;
 2:    use overload OPERATOR => SUBROUTINE,
 3:                 OPERATOR => SUBROUTINE,
 4:                 ...
 5:        fallback => 1;

Grundlegende Verwendung

 1:    package MyApp::User;
 2:    use overload q{""} => 'as_string'
 3:        ;
 4:
 5:    sub as_string {
 6:        $_[0]->{name}
 7:    };

Weitere Beispiele für Operator-overload

    *

     1:  print $user_b ~~ 'admin';
  • Komplexe Zahlen (Math::Complex)

  • Rationale Zahlen (Math::BigRat)

Overload für Vergleichsoperatoren

Definition:

 1:    $user_a > $user_b

soll bedeuten "$user_a ist älter als $user_b"

 1:  @user_nach_alter = sort { $a <=> $b } @users;

Overload für Vergleichsoperatoren

 1:  use overload
 2:    '<=>' => 'cmp_timestamps'
 3:    
 4:    ;
 5:  
 6:  sub cmp_timestamps {
 7:      $_[0]->{seit} <=> $_[1]->{seit}
 8:  }

Overload für Vergleichsoperatoren

 1:  use overload
 2:    '<=>' => 'cmp_timestamps'
 3:    fallback => 1, # für '>', '<', '=='
 4:    ;
 5:
 6:  sub cmp_timestamps {
 7:      $_[0]->{seit} <=> $_[1]->{seit}
 8:  }

Demo der Umleitung

Dereferenzierung (oder "X für U")

 1:  my $ref = ..;
 2:  
 3:  $ref->[0] # Array-Referenz

Dereferenzierung (oder "X für U")

 1:  my $ref = ..;
 2:  
 3:  $ref->[0] # Array-Referenz
 4:  
 5:  $ref->{max} # Hash-Referenz

Dereferenzierung (oder "X für U")

 1:  $ref->... # Dereferenzierung
 2:
 3:  use overload '@{}'; # Array
 4:  use overload '%{}'; # Hash
 5:  use overload '${}'; # Skalar
 6:  use overload '&{}'; # Code

Dereferenzierung (oder "X für U")

Wofür?

 1:    sub find {
 2:        my ($name) = @_;
 3:        return MyApp::Query::Delayed->new(<<'SQL', $name);
 4:            SELECT *
 5:            FROM users
 6:            WHERE name LIKE ?
 7:    SQL
 8:    }
  • liefert eine "verzögerte" Abfrage zurück

  • Die Abfrage wird erst beim Aufruf von ->fetch ausgeführt

Verwendung der verzögerten Abfrage

<07-overload.pl>

 1:    ...
 2:    my $users_a = find('a%');
 3:    $users_a->fetch();
 4:    print Dumper $users_a->data->[0];

Dereferenzierung

Expliziter Aufruf ist unpraktisch:

 1:    $users_a->fetch();

Schöner wäre ein automatischer Aufruf von ->fetch bei Zugriff auf $users_a->[...].

Dereferenzierung

Expliziter Aufruf bei Zugriff als Array(referenz):

 1:    use overload '@{}';
 2:
 3:    sub as_array {
 4:        my ($self) = @_;
 5:        $self->fetch;
 6:        $self->{data}
 7:    };

Demo

<08-overload-array.pl>

Dereferenzierung

 1:    my $max = find('Max');
 2:    # Es gibt höchstens einen!
 3:    print $max->[0]->{name};

Dereferenzierung

 1:    my $max = find('Max');
 2:    # Es gibt höchstens einen!
 3:    print $max->[0]->{name};

Idee:

 1:    $max->{name}

soll das selbe sein, wie

 1:    $max->[0]->{name}

Dereferenzierung

Idee:

 1:    $max->{name}

soll das selbe sein, wie

 1:    $max->[0]->{name}

Implementation

 1:  use overload '%{}' => 'as_hash';  
 2:  sub as_hash {
 3:      $_[0]->[0]
 4:  };

Kleines Problem

 1:    $max->{name}
 2:    sub as_hash {
 3:        $_[0]->[0]
 4:    };

Kleines Problem

 1:    $max->{name}
 2:    sub as_hash {
 3:        $_[0]->[0]
 4:    };
 5:    sub as_array {
 6:        my ($self) = @_;
 7:        $self->fetch;
 8:        $self->{data}
 9:    };

Kleines Problem

 1:    $max->{name}
 2:    sub as_hash {
 3:        $_[0]->[0]
 4:    };
 5:    sub as_array {
 6:        my ($self) = @_;
 7:        $self->fetch;
 8:        $self->{data} # Uhoh
 9:    };

Lösung

  • 1. Rebless in nicht überladene Klasse

  • 2. Zugriff auf Hash

  • 3. Zurückblessen

Demo

09-overload-hash.pl

 1:    sub __field {
 2:        my ($self,$field,$value) = @_;
 3:        my $class = ref $_[0];
 4:        bless $self, __PACKAGE__ . "::Unblessed";
 5:        if (@_ == 3) {
 6:            $self->{$field} = $value;
 7:        };
 8:        my $res = $self->{$field};
 9:        bless $self => $class;
10:        $res
11:    }

Zusammenfassung

  • overload leitet die Werteverwendung auf Perl Code um

  • String, ~~

  • Arithmetische Operationen

  • Vergleichsoperationen

  • Verwendung als Referenz

Übersicht

  • tie

  • overload

  • Transparente Objekte mit tie+overload

Kombination

Kombination der beiden Techniken:

Zugriff auf Variablen: tie

Verwendung von Werten: overload

Kombination

Kombination der beiden Techniken:

Zugriff auf Variablen: tie

Verwendung von Werten: overload

Totale Kontrolle über Werte und Verwendung

Anwendungsstruktur

 1:  Perl <-> FireFox     <-> Website
 2:
 3:  alert( window.tab
 4:         .linkedBrowser
 5:         .document
 6:         .innerHTML)
 7:
 8:  print $window->{tab}
 9:        ->{linkedBrowser}
10:        ->{document}
11:        ->{innerHTML}

Ueberblick fuer Implementation

  • Voraussetzungen

  • Implementation der transparenten Objekte

  • Performance

  • Anwendungsmöglichkeiten, Ausblick

  • (Weitere Features)

Voraussetzungen

Benoetigte Funktionalitaet

  • Javascript String eval

  • Lesen und Setzen von Objekteigenschaften

  • Aufruf von Methoden

Anwendungsstruktur II

Anwendungsstruktur II

Anwendungsstruktur II

Anwendungsstruktur II

Anwendungsstruktur II

REPL - Read Eval Print Loop

Anwendungsstruktur II

REPL - Read Eval Print Loop

Anwendungsstruktur II

REPL - Read Eval Print Loop

Die Mitte der Brücke

Die Mitte der Brücke

Die Mitte der Brücke

Javascript Object Notation als Serialisierungsformat

JSON

  • Skalare

     1:  "Hallo", 123
  • Arrays

     1:  [ ... ]
  • Hashes

     1:  { foo: "bar", baz: "blurble" }
  • Kombinationen aus allen dreien

JSON

Einschränkungen

  • Objekte sind Hashes

  • Keine Filehandles

  • Keine Objekttypen

Anatomie der Kommunikation

Serielle Kommunikation

Anatomie der Kommunikation

Serielle Kommunikation

  • Perl initiiert die Kommunikation

Anatomie der Kommunikation

  • Perl initiiert die Kommunikation

  • Perl schickt den Befehl an Javascript

Anatomie der Kommunikation

  • Perl initiiert die Kommunikation

  • Perl schickt den Befehl an Javascript

  • Javascript verarbeitet den Befehl

Anatomie der Kommunikation

  • Perl initiiert die Kommunikation

  • Perl schickt den Befehl an Javascript

  • Javascript verarbeitet den Befehl

  • Javascript sendet das Ergebnis zurück

Anatomie der Kommunikation

  • Perl initiiert die Kommunikation

  • Perl schickt den Befehl an Javascript

  • Javascript verarbeitet den Befehl

  • Javascript sendet das Ergebnis zurück

  • Perl verarbeitet das Ergebnis

Perl-Seite

  • Annehmen des Befehls und der Parameter

  • Verpacken der Parameter in JSON

  • Senden des Befehls und der Parameter an Javascript

  • ... Javascript sendet Ergebnis

  • Auspacken der Antwort aus JSON

  • Zurückliefern der ausgepackten Datenstruktur

Javascript-Seite

  • Lesen eines Befehls

  • Wandeln von JSON in Javascript Struktur

  • Ausführen des Befehls

  • Wandeln des Ergebnisses in Javascript Struktur

  • Rückgabe an Perl

Beispiel

Beispiel

Beispiel

Beispiel

Beispiel

Beispiel

Pädagogische Vereinfachungen

Pädagogische Vereinfachungen

Pädagogische Vereinfachungen

Knallharte Lügen

Zweites Beispiel

 1:  window
  • window ist ein Objekt

  • Objekte sind nicht über JSON serialisierbar

  • Und jetzt?

Objektverhalten

 1:  $window->open()     <==> window.open
 2:
 3:  $window->{title}    <==> window.title
 4:
 5:  $window->[3]        <==> window[3]

Objektverhalten

 1:  $window->open()     <==> window.open
 2:
 3:  $window->{title}    <==> window.title
 4:
 5:  $window->[3]        <==> window[3]

AUTOLOAD, overload + tie

Methodenimplementation

 1:  $window->open()

Methodenimplementation

 1:  $window->open()

AUTOLOAD

 1:  sub AUTOLOAD {
 2:    my ($self,@args) = @_;
 3:    my $method = $AUTOLOAD;
 4:    $self->bridge->callMethod($self,$method,@args);
 5:  };

Methodenimplementation

 1:  $window->open()

AUTOLOAD

 1:  sub AUTOLOAD {
 2:    my ($self,@args) = @_;
 3:    my $method = $AUTOLOAD;
 4:    $self->bridge->callMethod($self,$method,@args);
 5:  };
 6:  
 7:  JS: repl.callMethod(repl.getLink(42),'open');
 8:  
 9:  JS: return JSON.stringify( repl.wrapResult( 
10:        repl.callMethod(repl.getLink(42),'open')));

Attributzugriff

  • AUTOFETCH

  • Gibt's nicht

overload und tie

  • overload - Wertverwendung auf Routine umleiten

  • tie - Variablenzugriff auf Routine umleiten

  • overload liefert getiete Variablen zurück

  • tie reicht Daten an Firefox durch und verpackt die Daten in Objekten mit overload

overload

  • overload

    Erlaubt es, Objektzugriff auf eine Subroutine umzugbiegen

  • use overload '%{}' => '__as_hash'

     1:  sub __as_hash {
     2:    my ($self) = @_;
     3:    tie my %tie, 
     4:      'MozRepl::RemoteObject::TiedHash', $self;
     5:    %tie
     6:  }

tie

tie erlaubt es, Variablen wie Objekte zu behandeln.

 1:  Zugriff auf Variable      -> Objektmethode
 2:  $window{ title }          -> FETCH(%window, 'title')
 3:  $window{ title }='Hallo'  -> STORE(%window, 'title', 'Hallo' )

overload und tie für Hashes

Beispiel

 1:  $window->{title}

overload und tie für Hashes

Beispiel

 1:  $window->{title}

==>

 1:  $window->__as_hash()->FETCH('title')

overload und tie für Hashes

Beispiel

 1:  $window->{title}

==>

 1:  $window->__as_hash()->FETCH('title')
 2:        return getAttr($id, 'title')
 3:        return $firefox->eval("
 4:          repl.wrapResult(repl.getLink($id)['title'])
 5:        ");

overload und tie für Arrays

use overload '@{}' => '__as_array'

 1:  sub __as_array {
 2:    my ($self) = @_;
 3:    tie my @tie, 
 4:      'MozRepl::RemoteObject::TiedArray', $self;
 5:    @tie
 6:  }

tie

 1:  Zugriff auf Variable   -> Objektmethode
 2:  $array[ 3 ]            -> FETCH(@array, 3)
 3:  $array[ 3 ] = 'Hallo'  -> STORE(@array, 3, 'Hallo' )

overload und tie zusammen

 1:  overload     tie
 2:  @{}          Array-Reflektor auf getAttr / setAttr
 3:  %{}          Hash-Reflektor  auf getAttr / setAttr

Zwischenstand

 1:  print $window->{title};
 2:  $window->{tab}
 3:         ->{linkedBrowser}
 4:         ->{document}
 5:         ->{innerHTML} = 'Hello World';

Demo

10-demo-firefox.pl

Javascript hat Funktionen

 1:  var open = window.open;
 2:  open('http://corion.net');
 3:  // window.open('http://corion.net')

Und Perl?

 1:  my $open = $window->{open};
 2:  $open->('http://corion.net')

Funktionsvariablen für Perl

 1:  my $open = $window->{open};
 2:  $open->('http://corion.net')

Overload auch für Funktionsaufrufe:

use overload '&{}' => '__as_code'

 1:  sub __as_code {
 2:    my ($self) = @_;
 3:    return sub {
 4:      $self->bridge->applyFunc($self,@_)
 5:    }
 6:  }
 7:
 8:  my $open = $window->{open};
 9:  $open->('http://corion.net')

Zurück zur Struktur

Zurück zur Struktur

Zurück zur Struktur

Zurück zur Struktur

Zurück zur Struktur

Zurück zur Struktur

Zurück zur Struktur

Zurück zur Struktur

Zurück zur Struktur

Wann fertig?

  • Objektbrücke sammelt JS Objekte

  • Referenzen in JS bleiben ewig am Leben

  • Speicherlöcher

Wann fertig?

Bild: (Zerbrochene Kette)

Wann fertig?

Bild: (Zerbrochene Kette)

Wann fertig?

Bild: (Zerbrochene Kette)

Wann fertig?

Bild: (Zerbrochene Kette)

Funktionen der Javascript Brücke

Verwaltung der Objekte

  • wrapResult

     1:  { result: "1", type: "object" }
  • getLink

     1:  [ id -> object ]
  • link

     1:  [ id <- object ]
  • breakLink

     1:  [ id -> undefined ]

Zusammenfassung des tatsächlichen Flusses

Zusammenfassung des tatsächlichen Flusses

Zusammenfassung des tatsächlichen Flusses

Zusammenfassung des tatsächlichen Flusses

Zusammenfassung des tatsächlichen Flusses

Zusammenfassung des tatsächlichen Flusses

Performance

  • Jedes Lesen eines Attributs läuft einmal durch die ganze Kette

  • Hohe Latenz

  • Jedes Schreiben

  • Jeder Funktionsaufruf

  • Jede Freigabe eines Perl Objekts

Performance II

Beispiel:

 1:  $window->{document}->{body}->{title}
  • 3 Lesezugriffe

  • .document

  • .body

  • .title

  • 2 Objektfreigaben

  • .document

  • .body

Beispiel

HTTP::Cookies::MozRepl - Aufzählen der Cookies

 1:  while ($iter->hasNext) {
 2:    $iter = $iter->getNext();
 3:  
 4:  }

Beispiel

HTTP::Cookies::MozRepl - Aufzählen der Cookies

 1:  while ($iter->hasNext) {     # 1x hasNext() aufrufen
 2:    $iter = $iter->getNext();  # 1x getNext() aufrufen
 3:                               # 1x altes $iter freigeben
 4:  }

3 Anfragen pro Cookie!

Recap

  • Objekte müssen nicht tatsächlich in Perl sein

  • tie + overload kann Objektbäume vorspiegeln

  • overload fängt alle Zugriffsarten ab

  • tie liefert passende Werte zurück

Zukunftsmusik

  • "Richtiges"/kooperatives Event-System (AnyEvent)

  • Integration mit XUL::Gui

Danke!

Fragen?

Bildverweise

Stasi 2.0, "Schäublone", Dirk Adler, http://www.dataloo.de/stasi-20-525.html

Perl Onion, The Perl Foundation

Bonus Section

Events und Callbacks

 1:  $window->{onload} = sub {
 2:    print "Seite geladen";
 3:  }

Demo

Wie funktioniert das?

Events und Callbacks

 1:  $window->{onload} = sub {
 2:    print "Seite geladen";
 3:  }

Wie funktioniert das?

  • Javascript hat Callbacks und anonyme Funktionen.

  • Perl-Funktionen bekommen IDs (cbid)

  • Javascript-Proxy-Funktion zu IDs

  • Javascript-Proxy-Funktion senden Event an Perl

  • Perl führt den Callback aus

Events und Callbacks

 1:  sub { ... }  -> [Brücke] (callback id) --> Javascript Proxy
 2:                                                   |
 3:                                        window.onload = proxy;
 4:    ~~~                                           ~~~
 5:                                           window.onload();
 6:                                                   |
 7:                                                   v
 8:                                eventQueue.push([arguments,e])
 9:                                                   |
10:                                                   v
11:                 [Javascript] eventQueue  <--------+
12:
13:  $repl->poll() ---> 
14:               <---  { result:{}, events: [...] }
15:  $repl
16:    ->dispatch_events();

Schrittweiser Fluss

 1:  $document->{title}
 2:
 3:     M:R:Instance            [ ->__as_hash, M:R:TiedHash -> ]
 4:  -> MozRepl::RemoteObject   [ repl.getAttr, $document, 'title' ]
 5:  -> JSON                    [ repl.getAttr, repl.getLink(42), 'title' ]
 6:  -> Net::Telnet -> mozrepl
 7:  -> JS Brücke, storedObjects[] [ return repl.getAttr(repl.getLink(42),'title') ]
 8:  -> Javascript              [ document.title ]
 9:  -> JS Brücke               [ JSON.stringify(repl.wrapResults( document.title )) ]
10:                             [ { result: 'Hello', type: 'string' } ]
11:  -> mozrepl -> Net::Telnet
12:  -> "hello"

Wer bin ich? (Objektidentität)

 1:  my $doc1 = $window->{document}; # id 43
 2:
 3:  my $doc2 = $window->{document}; # id 44
 4:
 5:  $doc1 == $doc2 ?

objectIdentity

Javascript === Operator

Weitere Anwendungsmöglichkeiten

  • Javascript Validation

  • Reagieren auf Adressbalken

  • Filtern von eBay-Auktionen

Weitere Anwendungsmöglichkeiten

  • Nicht nur für FireFox

  • Thunderbird (Mailablage)

  • ChatZilla (irc)

 1:  $window->{linkedBrowser}->{tab}{document}->{title}
 2:  
 3:  getLink($window)
 4:  getAttr('linkedBrowser')
 5:  breakLink($window)
 6:  getAttr('tab')
 7:  breakLink($linkedBrowser)
 8:  getAttr('document')
 9:  ...

Lokale Implementation

 1:  ->__dive(qw( linkedBrowser tab document ));
 2:  
 3:  repl.dive(...)

Schönere Fehlermeldungen

Schrittweiser Fluss (Objekt)

 1:  $window->{document}
 2:
 3:     M:R:Instance            [ ->__as_hash, M:R:TiedHash -> ]
 4:  -> MozRepl::RemoteObject   [ repl.getAttr, $document, 'title' ]
 5:  -> JSON                    [ repl.getAttr, repl.getLink(42), 'title' ]
 6:  -> Net::Telnet -> mozrepl
 7:  -> JS Brücke, storedObjects[] [ return repl.getAttr(repl.getLink(42),'title') ]
 8:  -> Javascript              [ document.title ]
 9:  -> JS Brücke               [ JSON.stringify(repl.wrapResults( document.title )) ]
10:                             [ { result: 43, type: 'HTMLDocument' } ]
11:  -> mozrepl -> Net::Telnet
12:  -> bless 43, 'M:R:I'

Beispiel

??? Gutes Beispiel, das direkte Callbacks demonstriert

z.B. Enumeration?

Beispiel 2

??? Gutes Beispiel, das asynchrone Callbacks demonstriert

z.B. Timer?